Developing Applications On Ethereum Blockchain - Pluralsight (9409)
Developing Applications On Ethereum Blockchain - Pluralsight (9409)
by Ivan Mushketyk
Applications on Ethereum Blockchain
Start Course
Bookmark Add to Channel Download Course Schedule Reminder
Course Overview
Transcript links
Course Overview
Hi everyone. My name is Ivan Mushketyk, and welcome to my course, Developing Applications on Ethereum
Blockchain. I am a senior software engineer with years of experience developing traditional and
Blockchain‑based applications. In the last several years, we've witnessed a rapid development of the new
ecosystem of decentralized applications. Ethereum is at the very center of this revolution and is the most
popular platform to develop decentralized applications. In this course, we're going to learn how to develop
applications for Ethereum blockchain. Ethereum, as other Blockchain projects, has a steep learning curve, but
this course starts from the very foundations of this technology and progresses to more and more complex
features and covers other related topics as well. Some of the major topics that we will cover include foundations
of the Blockchain and the Ethereum technologies, Solidity programming language, developing smart contracts
with Solidity, Truffle framework, and how to build web applications with Ethereum. By the end of this course, you
will know how to develop nontrivial applications for Ethereum and will be able to implement full stack
applications for this platform. Before beginning this course, you should be familiar with at least one
object‑oriented programming language and have at least basic knowledge of web development. I hope you'll
join me on this journey to learn how to write decentralized applications for Ethereum with the Developing
Applications on the Ethereum Blockchain course at Pluralsight.
Ethereum Protocol
Introduction
Hi. My name is Ivan Mushketyk, and I'm excited to welcome you in my course, Developing Applications on
Ethereum Blockchain. And let's start right off the bat, why learn Ethereum? Ethereum allows us to build a new
breed of applications called decentralized applications. Just as regular applications, they run on multiple
machines. But what differs them is that these machines are not owned by a single group or organization. They
are owned by different people in different organizations, and anybody across the world can provide
computational resources for the network. No single actor controls the whole application or the whole network.
And having this network allows us to build applications on top of it. But why would we be interested in building
decentralized applications? Well, there are multiple reasons, but here are the most important. First of all,
decentralized applications are more reliable. There is no single point of failure, and they will continue to run even
if some machines will go down. They are more secure. Code and data are cryptographically secured, and
nobody can amend them. And the last, they are transparent. Any participant can verify that everyone is playing
by the rules and not, for example, try change data that should not be changed. All of these properties create a
sort of a trust layer. They create more trust in applications and more trust in users that are using them. But why
learn Ethereum specifically? Well, first of all, it allows us to create decentralized applications. It is a
multi‑billion‑dollar industry, and new players enter the field literally every day. Ethereum has a huge community
that is improving platform constantly. And the last, but not the least, Ethereum is one of the most in‑demand
skills in software engineering. Now let's look at some examples of applications that we can build using Ethereum.
First one could be an online voting, which would allow us to ensure that nobody has amended the results of the
voting. The second one is self‑executing legal agreements that do not require lawyers, and anybody can verify
that a legal agreement was executed exactly as it was specified. A very popular set of applications is called DeFi,
or decentralized finance, which is a set of financial applications on top of Ethereum. And the last example we will
talk about is digital ownership. We can create a provable record of who owns what, and nobody can maliciously
amend those records. Now let's take a very high‑level overview of Ethereum. At the bottom of Ethereum is an
Ethereum network. It has applications code and data used by these applications. The next layer is called Web3
API, or application programming interface. It allows to programmatically interact with Ethereum. And then using
Web3, we can build client applications and server‑side applications that interact with Ethereum. I hope that this
has piqued your interest in Ethereum just a little bit so you must be eager to know what we are going to learn in
this course. So we will start this course by looking into Ethereum protocol. And Ethereum protocol is the basis of
Ethereum network. Then, we will look into smart contract development, and smart contracts are executable bits
of code that run on Ethereum. We will learn a programming language called Solidity that is used to develop
smart contracts. We will then see how we can create a development environment for Ethereum to become more
efficient in developing smart contracts. And then we will look into how we can develop even more advanced
smart contracts. Now all of these topics, they will constitute the server‑side of our decentralized application. At
the end of the course, we will see how we can build a web application built on Ethereum. Now what do you need
to know to get the most out of this course? This course is for software developers, first of all. So you should be
familiar with at least one programming language. And you also need to know the basics of web development,
which will be important for the last part of this course. Now here is what you don't need to know. You don't need
to know anything about blockchain or Ethereum. We will cover everything in this course from scratch. You don't
need to be an expert in JavaScript. We will use it to write some simple scripts and tests, but I will cover all
nontrivial JavaScript features that we will use in this course. And in the last module, we will build a web
application using React, but you don't need to know React to follow along. Even more, we will cover the bare
minimum fundamentals of React so you can follow along with the demo and understand how a React‑based
web application works.
Blockchain Technology
Before we start talking about the Ethereum, let's briefly talk about what is blockchain. And this is important
because blockchain is a foundational technology for Ethereum. We will learn about how it was developed, and
we will talk about Bitcoin, the first application built on blockchain. We will then talk about how Blockchain works
in a nutshell. The history of blockchain starts in 2008 when an anonymous person named Satoshi Nakamoto
published a Bitcoin whitepaper. Up until this day, nobody knows who Satoshi Nakamoto was. But this paper that
he wrote described how to build a decentralized currency. This was a major breakthrough in cryptography. It
was the first working approach for how to build a digital currency that has no master of central nodes. This paper
also described blockchain technology. Following the whitepaper, in 2009, Bitcoin was launched, and it became
the first decentralized currency in the world. The development of Bitcoin ignited the cryptocurrency space. And
in 2011, there were already more cryptocurrency projects. Some of them were just clones of Bitcoin, but some of
them were more specialized projects. And by the way, when I'm saying a cryptocurrency, that is the same as
decentralized currency. Slowly, but steadily, Bitcoin was gaining more and more adoption. And in 2014, more
businesses and users started adapting Bitcoin. Now before discussing Bitcoin in more detail, let's discuss an
alternative to Bitcoin like a bank. Now if you want to send money, you can't do it directly. You need to rely on a
centralized financial institution, like a bank. The bank, in turn, is responsible for tracking financial transactions and
balances for its users and provides services like payments. But why would Satoshi Nakamoto would want to
design an alternative? Well, there can be a few reasons. First of all, the banking system requires a total trust,
which may not be an issue in developed countries, but can be quite an issue in developing countries.
Transactions in a banking system are quite slow and may take sometimes days to process, especially if this is an
international transfer. And a banking system has quite high fees. And how does Bitcoin compare to a banking
system? Bitcoin is just a network of machines, and this network stores a registry of assets. It stores who owns
what amount of Bitcoin. But there is no need to trust any individual, actor, or entity in this network. None of these
actors control the whole network. We can instead trust the algorithm that is underpinning the network. Using
this Bitcoin network, users can send payments to each other using Bitcoin as the currency. Now let's briefly
mentioned the main characteristics of Bitcoin. Bitcoin is a public registry of assets and transactions. Everybody
can see what transactions took place and what is the balance of each account. The Bitcoin network is
autonomous, and it is controlled by software and not by people. So even if there is somebody with malicious
intent, they cannot compromise the network. Bitcoin is permissionless, and anybody can join the network either
to use it for payments or to provide more computational resources for the network. And now the last, but not the
least, Bitcoin is cryptographically secure. Nobody can change the history of transactions, take their Bitcoins,
amend the transactions, etc. Now let's briefly talk about blockchain, the technology that underpins Bitcoin. As
the name suggests, a blockchain is just a chain of blocks. Every block has a list of transactions that took place,
and they're grouped into a block. A block also has a pointer to a previous block that happened before it. If we
follow this chain of blocks, we'll eventually get to the very first block that is called the genesis block. If we
traverse this chain in the opposite direction, we would be able to see what were the balances for each account
at any point in time. But who generates those blocks? Well, there is no central node in the system that generates
those blocks. Instead, Bitcoin is a peer‑to‑peer network. Everybody is equal in Bitcoin. Every node is connected
to a subset of nodes. And if somebody wants to send a transaction, they would just broadcast it to the network
through its peers. Those peers would validate incoming transactions, check that a sender has Bitcoins they want
to send, and then they broadcast it further to their peers. Later at some point, a block is generated in the
network. It is not generated by a single machine. Instead, there is a process that temporarily elects the leader.
Well, this leader generates a new block, and it stores it in its own storage and then broadcasts it to its peers until
the block is broadcasted to all other peers. And every peer will validate that the block is correct. And only if they
confirm that the block is correct, they store it locally to their storage, and it becomes the part of their state. Now
it is interesting to discuss what operations we can do with the blockchain. First of all, we can read historical
transactions, and we can create new transactions, and then these transactions are grouped into new blocks.
What we can't do, we cannot go back into the past and update all transactions. And we also cannot delete all
transactions and, say, pretend that this transaction never happened.
Hash Functions
Before we start talking about Ethereum, let's briefly talk about how a blockchain achieves its properties such as
immutability. At the core of it is a concept of a hash function. A hash function is just an algorithm that can take an
input of any length, and as a result, it generates a very long number. There are more multiple types of hash
functions, but all of them have the same properties. One property is that for the same input, a hash function will
always generate the same output. If we change the output just slightly, a hash function will generate a
completely different output. It will have the same length, but different value. It is important to understand that
the output value of a hash function or a hash value is not an encrypted data. We can compute the hash value
using a hash function, but it is impossible to go the other way around and get the input if we have just the
output. Now let's talk about the properties of hash functions. First of all, hash functions are very fast. An output
of a hash function is very small in size. Depending on the hash function, it is usually between 20 and 64 bytes.
The other important property is that it is impossible to find two inputs that would result in the same hash value.
And all these property together, they allow to use hash functions to compute digital fingerprints. For example, if
you have a file and you want to know if it has been changed, you can just compute its hash value. And if it's the
one that you expected, it means that the file has not been changed, and if it is different, it means that somebody
has changed it and you have a different hash value. And here's how blockchain uses hash functions. Each block
contains a hash value of the previous block, and this is how a blockchain maintains links between the blocks. If
someone tries to amend a block and, say, change the transaction, then this block will now get a different hash,
and this is why it is completely impossible to secretly change passed transaction or change blockchain data.
Ethereum Overview
Now once we've covered all the basics, like blockchain and hash functions, let's talk about Ethereum. And here,
we'll, first of all, talk about the rationale for Ethereum. Why would someone come up with an idea for this? We will
talk about how it was developed, and then we will have a 10,000 feet overview of how it works. To understand
how Ethereum was developed, let's look at the history of blockchain applications. At some point, blockchain
technology became very popular, but the problem is that every project was built on its own slightly‑different
blockchain implementation. For example, Bitcoin was useful for payments, and it had its own blockchain
implementation. Other projects like Namecoin, for example, that was based on blockchain to implement the
decentralized domain name system, again, had its own blockchain implementation. Another project called Nxt
for implementing financial services, again had its own blockchain as well. And the main problem here is that the
entry barrier for new applications was just too high. It was very difficult to develop your own blockchain for a new
application, so this is where Ethereum came up. It aimed to create a single blockchain that will allow to develop
applications on top of it. So instead of specializing for a particular application, it would be a platform for
developing any applications on blockchain. So, a way to think about it is those initial blockchain applications,
they were like old‑school pagers. It has one predefined function, and this function is built into it. Ethereum now is
like a smartphone. Instead of having one application or set of applications, it is a platform that allows developers
to build applications on top of it. Now Ethereum was designed in 2013. It was designed by the Vitalik Buterin.
And this is his real name. He is not an anonymous person. Ethereum development started in 2014. And in 2015,
there was the first release of the Ethereum network. Every major release of Ethereum has a code name, and this
release was named Olympic. Ethereum was slowly gaining adoption for the next five years until in 2020, there
was a so‑called DeFi Summer, a boom in activity in decentralized financial obligations. And in 2022, the
community has finished The Merge project, an upgrade of the execution layer of Ethereum that made it faster
and more efficient. Just like Bitcoin, Ethereum has its own currency. In case of Ethereum, it's called Ether, or ETH.
Just as Bitcoin, it supports financial transactions. But unlike Bitcoin, we can use Ethereum currency to pay for
computations on the Ethereum network. Ethereum is what is known as Turing‑complete, which is a sophisticated
term to say that you can just develop general‑purpose applications on top of Ethereum. And the way to think
about it is that Ethereum is like a cloud provider, you can upload your application to an Ethereum network, but
you have to pay with Ether and not with dollars or euros to run your applications on top of Ethereum. Now let's
look at how Ethereum works in a nutshell. In Ethereum, there are two types of participants. The first one is called
an externally‑owned account. An externally‑owned account is controlled by a person. It has a unique address
and an Ethereum balance, and this account can send Ether to other accounts, so similar to what we can do with
Bitcoin. Another type of participant is called a smart contract. And smart contracts, they are not controlled by
people, but they are controlled by code deployed on a Ethereum. Just like externally‑owned accounts,
contracts have unique addresses and Ethereum balance that they control. But they also have data associated
with them, which is called state, and they have methods that other participants can call. When a method is
called, it can change a state of a smart contract, send Ether, or interact with other contracts. Now Ethereum has
been around for quite some time, and it has some interesting projects, so let's briefly mention some of them. One
project is Auger. And Auger is a prediction market where people can bet on outcomes of real‑life events.
Another one is OpenSea. This is a marketplace for digital collectibles, which is an interesting concept when
something can be digital, and, at the same time, it can be a unique collectible item. The other interesting project
is called Uniswap, and this is a decentralized exchange for trading cryptocurrencies. And the other project is
called Compound, which is a fully‑decentralized platform for lending and borrowing. Before going to other
topics, I want to briefly mention the concept of Web 3. You may find this term when you read about Ethereum.
And the idea of Web 3 is that Ethereum and similar platforms are an evolution of the internet like we had Web 1,
where we just had static web pages, and now we are using Web 2 that consists of dynamic web applications.
Now, Web 3 doesn't have a specific definition, but what people usually mean when they refer to Web 3 is that
they refer to an internet based on smart contracts, native payments built within these applications, and digital
ownership of items on the network.
Ethereum Wallet
Now let's talk about how we can interact with an Ethereum network. To interact with an Ethereum network, we
need to use a special piece of software called a wallet. Despite its name, the wallet doesn't store any money or
ether, so the name is quite misleading. Instead, a wallet stores cryptographic keys that are used to sign
payments or transactions that you are sending to the network. So a better name could be a key store, but the
wallet is what everybody uses. There is no single wallet implementation for Ethereum; instead, there are dozens
of different wallets, each one with its own focus. And they're also developed for different platforms. There are
command‑line wallets, desktop applications, there are wallets that are run in a browser, and there are mobile
applications. But before we talk about how Ethereum wallets work, let's discuss how Ethereum accounts are
different from bank accounts. Well, bank accounts are issued by a bank, so there is an institution that creates an
account for you, while an Ethereum account is created by a user themselves, and it can be created by any user
at any time and any user can have as many accounts as they want. The bank count is mostly used for payments,
while an Ethereum account can be used for payments, but it can also be used to access Ethereum applications.
An important word of warning is that a bank account can be restored by a bank. So, for example, if you forgot a
password to your bank account, a bank can still help you to restore your password. An Ethereum account, on
the other hand, cannot be restored if the access is lost, so with Ethereum you are responsible for keeping it safe.
On the other hand, a bank account is controlled by a bank and a bank can block it or suspend it, etc. An
Ethereum account, on the other hand, cannot be blocked or deleted. And now let's talk about how an Ethereum
wallet works. A wallet is connected to several Ethereum nodes, and a node is pretty much just a computer
running Ethereum software. At some point, once a block is generated somewhere in the network, it is being
propagated to other nodes, and very soon it will reach your wallet. A wallet checks if there are any transactions
related to the accounts that it tracks, and if there are any such transactions, it updates the state of the account
in its local storage. Notice that the wallet doesn't store any funds, it just reflects the state of the network, and the
state of the network is stored on individual nodes. Now we've covered how wallets are processing payments, but
what is more interesting is to learn how a wallet creates those payments, and to do it, we need to talk about the
keys that a wallet is storing. When you create an account in a wallet, a wallet creates a so‑called private key, and
this is just a long number. A private key is used to digitally sign transactions that you send from a wallet, and it
should be private. Whoever has access to the private key will be able to sign transactions and control your
account. Now from a private key, a wallet creates a public key and an address. A public key is, as the name
suggests, it's public, and it can be used by anybody to verify that the sender of a payment controls a particular
account. Now if a wallet needs to send a transaction, it first of all creates this transaction, then it
cryptographically signs it using the private key, and then it sends a payment and a signature to the network, and
then any participant on the network can look at the signature and verify that the payment was sent by the
account owner. So anybody else on the network can verify that the payment is valid, but they cannot send
payments using your account. Now the system only works if you keep your private key a secret, so I urge you if
you use Ethereum, please keep your private key secret, otherwise, somebody might steal your account.
How to Get Ether
At this point, you might be wondering, how do you get ether? One way to get ether was to participate in the
Ethereum presale that happened just before the Ethereum network was launched, but it happened in 2014. If
you don't have a time machine and cannot travel back to 2014, another option is to purchase some E's for other
currency such as dollar, euro, or a cryptocurrency like bitcoin. You can do it on one of the various crypto
exchanges, and there is even a guide on an official Ethereum website that walks through how to do this. Another
option is to provide Ethereum infrastructure. Previously you could do this through the process called mining, but
after the merge, which was a major upgrade of Ethereum in 2022, it is now a different process called staking, and
we will discuss both mining and staking in way more details in one of the next modules. But for this course, you
don't need to spend any money. Instead, you can use test Ethereum network, which works just like a regular
Ethereum network, but Ethereum on it is free. There are multiple Ethereum test networks, but the most popular
test network at the moment is Goerli. It is very similar to the main network, and it will have a long‑term support,
meaning that it will receive all upcoming Ethereum updates, and this is a network that we will use in this course.
There are other test networks, and if you read other sources about Ethereum, you might encounter test
networks like Ropsten, Rinkeby, etc. However, all of these networks are already deprecated. Now you can also
create your own test network, and to do this, all you need to do is to run Ethereum software on multiple
machines and connect them together. To get test ether, we need to use a special service called a faucet, and all
we need to do is to send the request to a faucet, and we will get some test ether for free. We will see how to do
this in one of the next demos. Now, this however, works only for test networks, and if you see somebody
advertising a free ether giveaway on the main network, this is likely a scam. Notice that test networks are
completely isolated from the main Ethereum network. They have different history, different transactions,
different blocks, everything is different. You can have the same account on both networks, but they will have
different account balances. So if you get some test ether, it won't be reflected on the main network.
Installing Metamask
In this demo, we will see how to install an Ethereum wallet. We will see how we can install and use MetaMask,
and there are two reasons why we pick MetaMask specifically out of all other options. First of all, it has a browser
wallet, and we will use it later in the course to interact with the Ethereum network from a browser and to use it to
interact with web applications. And second of all, it is one of the most popular wallets, so it is useful to be at least
familiar with it. And then using this wallet, we will create an Ethereum account, and then we will use this account
in later demos. To install MetaMask, we first of all need to go to the official website and then click on the
Download button. This Download button will lead us to the extension web page for the browser that you are
using, and since I'm using Chrome on these demos, it directed me to the chrome web store page. Now if I click
Add to Chrome, it will ask me if I want to install MetaMask extension and I want to do this. Now, to start the
process, I need to click Get Started; agree or disagree if you want to share information about how MetaMask is
used, and I'll say I'll agree. And now it asks me if I want to create a new wallet or if I want to import an existing
wallet, and for the purposes of this demo, I'll show you how to create a new wallet. Now while creating a wallet,
MetaMask will generate a private key, and it will encrypt this private key in the browser. To encrypt a private key,
it asks me to provide a password. So here I've provided a password and I can click Create. I can skip this video.
What it did in the background, it created a private key, and now it gives me an opportunity to back up this
private key. The way it works is these Ethereum wallets encode a private key in a set of 12 words that are easy to
record or remember, and this is just another way to represent a private key. Here I show it to you because this is
a test account, but you should never share a private key for your own account in any format. What it asks me to
do now is to record this sequence of words that is also called a recovery phrase, somewhere in a secret and
secure location. Now once you've done this, you can click Next. And then it asks to repeat this secret recovery
phrase just to ensure that you have recorded it. Now once I've done this and MetaMask is satisfied that I have
recorded this recovery phrase, I can click Confirm, and we're done. We have created an account in MetaMask.
And this is the interface of MetaMask, so at the top it shows our account name, which is account ID, but it also
shows an account address, and we can use this address to send funds to this account. We can share it with
other users so they can transfer Ether to us. There are other controls that allow us to buy and send Ether, and
there's a list of different digital assets at the bottom, and the only entry here is Ethereum, which we have none
for now. And this is it, we can close this tab, and if at any point you want to interact with MetaMask, it should be
available here. And to make it accessible, all I need to do is to pin the plugin, and now we can always open the
MetaMask page by just clicking on this icon and it will show the same interface we saw before.
Using Ethereum
Now once we have MetaMask wallet installed, we will see how we can interact within an Ethereum network. To
do this, we will first see how we can get test Ether from a faucet, and then we will see how we can send the
payment from one Ethereum account to another one. And at the end of the demo, we will see how we can use
MetaMask to use a decentralized application. To get this Ether for the Goerli network, we will use this website. So
this website is goerlifaucet.com, and it is developed by Alchemy, which provides development solutions for
Ethereum and other blockchain platforms. To get this Ether, we first of all need to sign up for an account with
Alchemy, which I have already done, and then we need to provide an address where we want to send test Ether.
To get an address of our account, we need to go to MetaMask, then click on this button to copy and address to
clipboard. Now we have our account address, and we can just paste it here. Now I'll click Send Me ETH, and
we've got this nice animation from Alchemy, but now we can go and look at our account. It seems that there is
nothing here at the moment, but this is because we're looking at a different network. We are looking at Ethereum
Mainnet, but we actually need to change our network to Goerli. To do this, we need to click here, and as you can
see, we only have Ethereum Mainnet, and we don't have any test networks, so we first need to show these test
networks in MetaMask. To do this, we need to click here, and then switch this toggle for Show test networks.
Okay, now if we go back, we can select Test Network from this list, and let's select Goerli, and as you can see, we
already have 0.25 GoerliETH, and if you are going through these steps yourself and you got to this point and you
still see 0 ETH in your account, maybe just give it a bit more time, because there is a slight delay between
sending the funds and receiving the funds. All right, so we have Test Ether, so let's try to send some of it to
another Ethereum account. To send Ether, I've created another Ethereum account in a different browser, and
here it is. And to send ETH to it, we need to copy the address of this account, go back here, click Send to send
Ether, then provide an address of this different account, and select the amount of ETH we want to send. And
let's just send 0.01 of an Ether. Now let's click Next, and now we see a confirmation page where we need to
confirm that we indeed want to send Ether to this other account. And a few things to notice here are that we
see this Estimated gas field that specifies how much we will pay for this transaction if we decide to send Ether,
and then we see the total amount of Ether that will be collected from our account to send this transaction. We
won't dive deeply into how gas and fees work, but we will cover this in depth in the next module. So now let's
click Confirm, and as you can see, here we have a pending transaction that is being processed by the network,
so let's just give it a few seconds to complete. Just a few seconds later, this transaction was successfully
executed. Now let's go to our second account and see if it received our payment. And as you can see, the
amount of ETH has increased, and we have a new record in the Activity tab that shows that this account has
received 0.01 of an Ether. Now we saw how we can send transactions using a Ethereum, let's see how we can
interact with a decentralized application. And as an example, we will see how we can use ENS or Ethereum
Name Service. ENS is similar to DNS, which is a Domain Name Service, and its goal is to record human‑readable
names for Ethereum addresses. Now to use it, we need to go to this website, ens.domains, and then we need to
go to the application by clicking this button. Now this is the ENS, the centralized application, and as you can see,
it looks just as a regular web application. Now, the only big difference is that to use this application, we need to
connect our MetaMask wallet with this application. ENS has already requested permission to connect MetaMask
wizard, and this process of connecting MetaMask simply means that we need to specify which accounts can be
used with this application. And I have only one account, and I'll select it and click Next, and it shows a warning
that this application will see addresses and account balances. If we connect it, it won't be able to send
transactions, but it will be able to ask for permission to send the transaction. And if I approve it, MetaMask will
sign it using the private key it stores, and it will send the transaction to the network. And its application is
connected, and now we can use it just as a regular web application. So, let's try to see if there is a name on the
ENS available, and I'll just select a very long name that is unlikely to have any matches. I don't think anybody is
interested in this particular name. And let's click Search, and as you can see, this name is available, so we can
click on it, and if we want to purchase this name, we need to first specify that we want to use a MetaMask wallet,
and we can now try to register our name. And as you can see, it asks if we want to send another transaction, and
now this transaction is actually to purchase this name. So we could, for example, register our account address
with this human‑readable name, but we won't do this. As you can see, the centralized applications, they look
very similar to regular web applications, with some differences around using a wallet. But now, once we've
covered how to use MetaMask as a user, how to interact within a serial network, let's see how we can develop
our own smart contracts.
Summary
In this module, we've covered introduction to Ethereum and blockchain. We've learned that Ethereum is a
platform for building decentralized applications and that Ethereum is based on blockchain technology. We can
build various applications on top of Ethereum, like payments, domain name system, financial applications, and
many, many more. We've learned that in the nutshell a blockchain is just a chain of blocks. Every block contains a
group of transactions and a pointer to a previous block. The current block points to the previous block and so on
until it reaches the first block that is called the genesis block. If we traverse the blockchain from the very first
block till the end, we will be able to explore the full history of transactions on the network. We also covered that
in Ethereum there are two types of accounts, externally owned accounts that are controlled by people and
smart contracts that are controlled by code. People can interact with contracts and other people, and smart
contracts can interact with contracts and other people as well. Now, after we've covered the basic theory of
Ethereum, we are ready to start learning about how to develop decentralized applications. And in the next
module, we will learn Solidity, a programming language that is used to build applications on Ethereum.
Getti n g
Contracts Started wi th Smart
Inroduction
Hi, this is Ivan Mushketyk with Pluralsight, and welcome to the next module of this course, Getting Started with
Smart Contracts. In this module, we'll move from theory to practice, and we will start implementing our first smart
contract. And if you recall, a smart contract is an executable code running on Ethereum network. And to do this,
we will use the Solidity programming language, the most popular programming language to implement smart
contracts. To become a proficient developer of smart contracts, it is important to learn how they are executed.
And that's why we'll also spend some time on learning about EVM, or Ethereum Virtual Machine, the software
that executes smart contracts. This module has two main goals. First of all, we will write a very simple smart
contract. This smart contract will only be able to store a single value, using Ethereum blockchain. Sort of a Hello,
World! application in the world of smart contracts. But we will also learn the basics of smart contract
development, a foundational skill that we will build upon in the next modules.
Smart Contracts
Now we will start learning about how to develop smart contracts in Ethereum. Just to remind you, smart
contracts are executable bits of code that live on the Ethereuem network. Each smart contract has code and
data associated with it, and in this way they are similar to objects in object‑oriented programming. Each contract
also has a unique address and a balance of ether that this contract controls. To develop smart contracts for
Ethereum, we can use one of the few programming languages. The most popular of them is Solidity, that is a
JavaScript‑like programming language, and Vyper, a security‑oriented Python‑like programming language. In this
course we will learn Solidity, and this is because this is the most popular programming language to write smart
contracts for Ethereuem, and it is even used for writing smart contracts for other blockchain platforms. Now,
here is how to create a smart contract. First of all, we need to provide a smart contract in Solidity and define
methods and state fields of a smart contract. Then, once we have a definition of a smart contract, we can deploy
one or multiple instances of it to the Ethereum network. And notice that once a smart contract is deployed, we
cannot add or remove methods from it, or we cannot add or remove its state variables. And in this way, smart
contracts are similar to statically‑typed languages like Java and not dynamic programming languages like
JavaScript.
Functions in Solidity
Now let's talk in more details about functions in Solidity. As in many programming languages, we can return
values from a function. To define a function that returns a value, we need to first use the returns keyword and
specify the return type in parentheses, and then in the body of the function, we can use the return keyword to
return a value from the function. We can also have a function that does not return anything, and in this case we
can just omit that returns keyword. In Solidity, we can also return more than one variable from a function, and
there are two ways to do this. One way is to provide two or more return types in parentheses, and if we want to
return multiple values from a function, we can list them in parentheses using the returns keyword. Solidity also
supports another syntax where we can specify names of return values, and if we do this, instead of using the
return keyword, we can assign return values using the names of return variables. Functions in smart contracts
can also call other functions. If we have a function like this that accepts two arguments, we can call it either
using a classic syntax where we specify a name of a function and then a list of arguments that we want to pass,
or we can use an alternative syntax where we can specify names of parameters, their values, and to do this, we
need to specify names and value pairs in curly brackets. Solidity also supports functions overloading, and
overloading is a case where we have several functions with the same name, but different parameter types, like in
this case where we have two functions named validate, but one of them expects a string and another one
expects an integer. If we want to call a validate function, Solidity will decide which one to execute based on the
parameters that are passed to a function.
Access Restrictions
The last topic that we will discover before we are going to implement our first smart contract are access
modifiers that specify who can access what methods on our smart contracts. And the reason why this is
important to discuss is because when we are deploying smart contracts on a public network, any participant can
have access to them and they can call any methods on our smart contracts. Since these method calls can
change a state of a smart contract, this might be dangerous. If you open access to all methods, anybody can
change the state of a smart contract in a way we don't want them to. A solution to this in Solidity is to specify
what methods can be called from the outside of the smart contract and what methods can be called only from
within the smart contract. And this solution works very similar to encapsulation in object‑oriented programming
languages such as Java, C#, etc. We've already covered the public function modifier, and just to remind you, it
means that anybody on the network, any participant can call this method. Now, if we don't provide a modifier,
this is the same as providing a public modifier, so these two definitions are equivalent. Now, external access
modifier is more restrictive than public modifier. It means that anybody from outside the smart contract can call
this method, but a smart contract cannot call this method itself. The last two modifiers, internal and private,
means that a method can only be called from inside the smart contract, but cannot be called from the outside of
it, and we will discuss the difference between private and internal later in the course when we will talk about
contracts inheritance in later modules. And yes, smart contracts can inherit each other like classes in
object‑oriented programming. We can also apply modifiers for individual fields, and public means that a value
can be read from the outside. It cannot be written from the outside if it is public. To change a variable, we need
to provide a method that actually changes it. Internal and private means that this variable can only be accessed
from inside the smart contract, and if we don't provide a modifier, it is different for fields than for methods. Fields
without modifiers are private by default.
Transaction on Ehtereum
Now let's talk about how we can interact with an Ethereum network. So far, this aspect was conveniently hidden
from us by Remix, but now we will learn about how it works, and we will see it in action later. No matter in what
way we want to change the state of the network, either to deploy a contract, or send Ether, or change a contract
state, we need to send a transaction. And here is a way to visualize it. A Ethereum network has a particular state.
If we want to change it, we need to send a transaction. Once a transaction is executed, it changes an existing
state of the network, and now we have a new Ethereum state. For performance reasons, Ethereum groups
transactions in blocks, and all transactions in a block are executed together. These are the same blocks, by the
way, that we've discussed previously where we were talking about the blockchain structure. Now I want to note
a few things about transactions execution. First of all, transaction execution is atomic; it either completes
successfully, or if it fails, it results in no changes to a contract state. Also, transactions on a smart contract
execute one after the other; they do not run in parallel so they cannot interfere with each other. To send a
transaction, a wallet forms a message and sends it to the network, and this message has a particular structure
that we will discuss now. First of all, a transaction has a nonce, and a nonce is just a transaction number from a
particular account. So for example, when a wallet sends a first transaction for the account, the nonce number is
0, then for the second transaction it will be 1, and so on. The second, the other parameter, is a gas limit, which
specifies a maximum amount of gas a user is willing to spend for a transaction execution, and we will talk about
how it works in one of the next videos. The other field is destination. This is an address of a receiver of a
transaction. It can be an address of a smart contract if we want to execute a method, or it can be an address of
another user if we want to send Ethereum. A transaction also has an amount field, and this is used for Ether
transfers. A transfer can be either to another user, or to a smart contract. The next three fields are v, r, and s, and
these fields together form a signature of a transaction. As we talked before, a signature is used to verify that the
center of a transaction is the owner of the account that sends funds or sends a transaction. And the last field
that we will discuss is called init or data, and it has a dual purpose. If a user wants to deploy a smart contract,
then this field will contain the byte code of a smart contract to deploy, or if a user wants to call a method on an
existing smart contract, this field will contain arguments for a method. Now let's see how smart contracts are
executed on Ethereum. The process starts with one of the users creating a transaction. This transaction contains
the byte code for a new contract, and it is sent to all of the peers of this user. The peers validate this transaction
and can either reject it or send it to other peers, so sooner or later, all nodes on the network will see this
transaction. At some point, one of the nodes will generate a new block, and this block will contain the byte code
of this new smart contract. This node will send this block to other nodes. Once this block is stored, we can now
execute methods on this smart contract. And the process for this looks very similar. If the user wants to call the
method, then a user creates a transaction. Its transaction is then sent to its peers that validate this transaction,
and then later, a block will be created that contains an executed transaction. Now, so far, we were talking about
how transactions look like and what fields they contain, but now let's talk about the structure of a block. A block
contains many fields, but we will talk about the most important fields. First of all, a block contains a block number,
and a block number is just an integer that is incremented for every new block. A block also has a hash of the
previous block, and this is the mechanism that we've discussed that links blocks together in a blockchain. As
we've discussed before, a block contains list of executed transactions that were executed in this block. And also,
a new block contains a new state of the network. And in case of Ethereum, the new state of the network is stored
in a tricky data structure called Patricia tree, and the way it works exactly is beyond the scope of this course.
Now the other field is a timestamp, and a timestamp specifies the time when this particular block was generated.
To wrap up the discussion of transactions, let's talk about smart contract limitations. Now, one limitation arises
from the fact that any participant can validate any transactions, and because of this, smart contract execution
should be deterministic, which means that a state change should depend only on the inputs in a transaction and
the current state of the network. This means that contracts cannot do things like access outside resources, like
sending an HTTP request, or they cannot generate truly random numbers by themselves. However, there are
solutions for this, and while we won't discuss them in this course, you can look into things like blockchain oracles
that try to solve this problem, and specifically, you can look into the work of the Chainlink company that provides
oracles for Ethereum.
Summary
In this module, we've covered the basics of smart contract development and implemented a simple smart
contract. As we've learned, every Ethereum node runs an Ethereum virtual machine, or an EVM for short. Every
Ethereum node also stores smart contracts, and these smart contracts are represented as byte code
instructions, which are just low‑level operations such as add, multiply, divide, and so on. In order to execute
these smart contracts, they should first be compiled into byte code, and to get byte code, we first need to use
one of the existing compilers to compile code into byte code. We've also learned that to change the state of an
Ethereum network, for example, to send the payment or to deploy a smart contract, we need to send a
transaction. These transactions are then grouped into blocks, and when transactions are executed, an Ethereum
network changes its state according to executed transactions. We've also covered that there are two ways to
interact with an Ethereum network, calls and transactions. A transaction can change a state of a network, but it
takes time to execute, and it takes Ether to process. Another option is to use a call operation. It is free and much
faster, but a catch here is that a call operation cannot change a state of a network. So in this module, we have
covered just the basics, but in the next module, we will dive much deeper into the Solidity programming
language. We will learn things like data structures, control structures, input validation, and so on. And we will use
this knowledge to build some more interesting and more complex smart contracts.
Solidity Programming Language
Introduction
Hi, and welcome to the next module of this course called Solidity Programming Language. In the previous
module, we've just scratched the surface of what we can do with Solidity programming language, and in this
module, we will dive deeper into Solidity. We will first look into data structures, such as arrays and mappings. We
will learn about control structures, such as ifs and loops. We will learn about structs, and structs is a way to group
several fields together in a single type. We will also learn how to perform input validation in Solidity, and we will
also use many other useful methods and language features. And to put our skills into practice, we will build a
simple voting smart contract on Ethereum. Now, without any further ado, let's get started.
Arrays
In this module, we will develop a smart contract for a simple voting app. In our application, anybody will be able
to cast a vote, and we will have a predefined set of options a user can choose from. And we also will only allow
one vote per Ethereum account. Now, to store voting data in our smart contract, we'll use arrays, a data
structure that we have not used before. Arrays in Solidity are very similar to arrays in other programming
languages. It is a way to store a sequence of elements in a smart contract. Now, all of these elements in an array
should have the same type, and this is where Solidity is different from other programming languages, such as
JavaScript, that allow us to store, say, integers and strings in the same array. Arrays allows us to get and set
elements by a numeric position, and they are a built‑in Solidity feature, so we don't need to use any third‑party
library. In Solidity, an array can be of one of two types. It can be a dynamic array that has a variable length, so it
can change as we remove or add elements to this array, or it can be statically‑sized array when the length of an
array is predetermined at compile time. First, we will discuss dynamic arrays, and to define a variable of a
dynamic array type, we need to specify a type of an element in the array, then use the square brackets and the
name of our variable, and this will create an empty array in Solidity. We can also create an array with a specific
size. So, for example, if we use this syntax and in parentheses we provide an initial size of an array, this will create
an array of size 2. Now, what we can do with an array, first we can get an element in an array by its position. And
keep in mind that in Solidity elements in array, they are numbered from 0. Then we can set an element in the
array, and to do this, we need to use this syntax. We need to provide the name of our array, then in square
brackets provide the position of an element we want to change, and the new value to write into this position. We
can also get a length of an array using the .length property. And we can add an element to the end of the array
using the .push method and provide a new element to add to an array. If we need an array with a specific fixed
size, then we need to use statically‑sized arrays. And to do this, we need to use a very similar syntax, but now,
instead of using empty square brackets, we need to provide a size of an array, and in this case, we'll create an
array that can only be of a length 5. It cannot shrink, it cannot extend its length. So with a statically‑sized array,
we cannot use the push method to add a new element to the end of the array, but we can still use all other
operators. We can get an element by its position, we can write an element to a specific position, and we can get
the length of our array. In Solidity, we can also define multi‑dimensional arrays. So, for example, in this case, I
would create two‑dimensional arrays because there are two pairs of square brackets. Solidity also defines types
for static byte arrays of various sizes that range from bytes1, which is a byte array of size 1, till bytes32, which is a
byte array of length 32. And it also has a type called bytes for a dynamicBytes array.
Reference Types
Now let's cover an important topic of value versus reference types. All types that we were using so far were
value types. Integers and Boolean, they're all value types. Most of the new types that we will learn in this module
will be reference types. So, arrays and structs, those of reference types. But what are the differences between
value and reference types? The main difference between them is what happens when we copy a value of each
type. If we copy a value type, it creates a copy of a variable, and if we change a copy, it doesn't change the
original variable. So, in this case, the value of the a variable remains unchanged. It is different with reference
types. If we assign an array, for example, to a different variable, it doesn't create a copy. In this case, both a and b
refer to the same array. This is why they're called reference types. If we change array b in this case, this will affect
both variables. And since we're going to use reference types a lot in this module, this is something to keep in
mind.
Memory
Now we'll have to talk about an important, but what can be a slightly confusing topic in Solidity, which is memory
and smart contracts. In Solidity, a variable can be stored in one of three places: memory, storage, and calldata.
So, let's see what is the difference between those three places. Well, memory is for temporary variables. They're
only stored for the duration of a call or a transaction, and as soon as it finishes, they disappear. However, memory
is very cheap to use. Storage, on the other hand, is for contract state. If we write something to storage, it is
persisted between transactions. However, the downside of storage, it is orders of magnitude more expensive
than using memory. And calldata is a special location for function parameters. Now it is immutable, we cannot
change it, and it is the cheapest to use among all three. Storage and memory have important implications for
reference types. Now, for example, this array in the smart contract field it is stored in storage. We don't need to
specify it explicitly. All contract fields are stored in storage. But if we define a local variable for an array, we need
to specify if we want it to be stored in memory or in storage. If we store a local variable in memory and assign a
contract field to it, then Solidity will copy data from storage to memory, and any changes to this array will modify
a copy. However, if we specify that we store our array in storage, and in this case, this assignment will not create
a copy, instead, if we make any changes to it, it will modify an original array. If we use reference types for input
parameters or outputs, we need to specify their memory types. For input parameters, it can be either calldata or
memory. Calldata is cheaper to use, but it is immutable. And we also need to specify storage type for return
values, which can be either memory or calldata as well.
Constructors
Before we implement the first version of the voting smart contract in Solidity, there are two more Solidity
features we need to learn. One of them is constructors. Constructors allows us to initialize fields of a contract
when it is deployed to a network. This should be a familiar construct to you. If you're familiar with other
object‑oriented programming languages, the main difference between Solidity and other programming
languages is that in Solidity, a smart contract can have only one constructor. Now, let's look at an example of a
constructor in a smart contract. To define a constructor, we need to use the constructor keyword, then provide a
list of parameters to constructor, and this works just as with a regular function, and in the body of a constructor,
we can specify how to initialize fields of our smart contract. Now, Solidity has a special type of field that can only
be changed in a constructor. These are called immutable variables. Immutable variables are similar to constants
that we have already discussed before. The main difference between them is that constants cannot be changed
anywhere in the smart contract while immutable variables can be changed in a constructor, but they cannot be
changed in any other function.
Input Validation
The last thing that we need to cover before starting working on our smart contract is how to reject invalid
transactions. Some users might try to execute invalid transactions. They might pass invalid arguments. For
example, they might try to vote for a non‑existing option in our voting contract, or some users might try to
change the state of a smart contract in an invalid way. Now, we need to reject those bad transactions. And to do
just that, Solidity provides multiple functions, such as revert and require and assert that we will learn in this
video. The main common thing between those functions is that if any one of them is executed, a transaction will
be stopped, and it will revert all state changes so far. Require is a function that you will probably use most often,
and it works like this. If you call require, you need to provide a Boolean expression. If this Boolean expression is
false, execution of the transaction will be stopped and reverted. Now we can optionally provide an error
message so if a transaction is reverted, a user might get more information about what happened with a
transaction. Now, the thing to keep in mind that when a transaction is reverted, the remaining gas is refunded to
a user, require is usually used to verify arguments passed to a function in the smart contract. But there is
another method that is used to verify an internal state of a smart contract to make sure that the state is
consistent. Assert and require, they work very similarly, and the biggest difference between them is that if assert
is executed, it will use up all the remaining gas. And the reason behind this is maybe if an internal state of a smart
contract is violated, then the user might have tried to do something malicious. So a developer of a smart
contract might decide to punish an attacker. Solidity has another function that is called revert. And unlike
require, revert does not accept the condition. If this function is executed, a transaction will be reverted without
any conditions. Now we can also provide a condition for revert by using an if statement. With Solidity, we can
also define a custom error. Now, these error types are defined like this. We use the error keyword, then the name
of an error, and then the list of parameters that an error has, and these parameters allow to store information
about why an error occurred so a user that sent a transaction might get more information about what happened.
We can also define a custom error without any parameters. Now, to use a custom error, we need to use the
revert keyword, provide the name of an error we want to use, and pass the values for the parameters of this
error, and we can use this key value syntax to specify the value of each parameter. If you read some old Solidity
code, you might encounter that throw keyword. Now, this keyword is deprecated. It was used in older versions of
Solidity, but nowadays you should use revert, require, and assert methods instead.
Voting
Now let's implement a simple voting smart contract using Solidity. In this smart contract, we will implement a
constructor that will allow us to initialize it, and we will also implement methods to vote and get voting results.
Now to start, I have already created a template for our voting smart contract. I have defined a constructor that
accepts a single parameter with a list of options that our users can vote for. And I've defined a method called
vote that our users can use to vote for one of the options. To start working on the smart contract, I need to
define the fields of the state of the smart contract. And here we will have two fields. First, we'll have an array of
votes, and each element in this array will contain a number of votes that users have casted for an option with a
specific position. And we'll also store a list of options that were used to initialize this smart contract. Now, once
we have our fields, we can initialize them in our constructor. First of all, we can initialize the options array, and to
do this, we just assign a parameter in our constructor to the state field. And then we need to initialize the votes
array. And to do this, I create a dynamic array of integers, and I create it with the same size as the list of options
that was passed to this constructor. Now to implement the vote function, what we need to do, first of all, we
need to record the vote, and to do this, we need to increment a number of votes for a particular option. To do
this, we'll read the number of votes that we have so far. We add 1 to this and restore it back to our array. But our
users can provide an invalid option number, so we need to protect ourselves from this. And to do this, we can
use the require function, and we can provide a condition that the option that was provided should be strictly
less than the length of the options array. We don't need to check if the option is negative because we use the
uint, which is an unsigned integer, which cannot be negative. And I'll also provide an error message here as well.
Okay, so now we can go to the Compile tab, and we can compile our smart contract. It seems like there were no
errors, so we can now deploy it. And notice that to deploy it, we need to provide an options list, which is the
parameter that we have in our smart contract. So to do this, I need to provide a list of strings, and I need to
encode it as a valid JSON, which I'll do like this. I'll define a JSON array, and I'll provide two values, coffee and tea.
So our users can vote for their favorite drink. And I'll click Deploy. And it seems that our contract has been
deployed, and if we go to the list of deployed contracts, we can see our smart contract. And notice that we have
three methods for our smart contract. Vote is the method that we have defined, and we have two methods for
the public fields that we have defined in our smart contract, but they won't return the arrays themselves. This
auto‑generated method allows us to provide a position of an element that we want to read. So, for example, if we
provide 0 here and click options, it will return the first option in the list of options. If we want to get the full array,
we will have to implement these methods ourselves. Now let's see how the vote method works. So let's vote for
option number 1, which is coffee. And it seems like transaction was executed successfully. And if we try to get
the number of votes for the first option, it now returns a 1. If we try to get it for the second option, it will return 0.
Now let's see how require works. So if we try to vote for an option that doesn't exist, then we will get an error
because we explicitly check that an option position is valid. Now we can also add two more methods that return
an option array and the votes array. Seems like no errors there. And now we can remove this smart contract and
deploy a new smart contract with these new methods. And it seems like it has been deployed. And now we have
two more methods, and if we call getOptions, we'll get the full array instead of getting individual elements.
Overflow Protection
Just to wrap up our discussion of reverting by transactions and error handling, we need to talk about overflow
protection. Overflow protection is an important safety check that is embedded into Solidity programming
language, and it ensures that numeric values we are trying to store can fit the ranges of the variables that we
use. And it is important because whatever numeric type we use, it only has a limited range of values it can store.
But why is it so important to embed it into the language itself? Well, because it can break some smart contracts.
For example, let's say in a smart contract, we're storing an amount of a contribution to an auction. Now if we
somehow try to write a value that is bigger than a particular type can store, that can lead to incorrect results.
Solidity has built‑in protection just for this specific case, and it is a relatively recent feature. It was only added to
version 0.8 of Solidity. So let's look into this using a simple example. Let's say we have an 8‑bit variable. An 8‑bit
variable in any programming language can serve values from 0 to 255. What happens if we store the maximum
variable already and we try to increment it by one? In Solidity, as in many other programming languages, what
will happen is that the value will overflow and v will now be equal to 0 instead of 256. But in the current Solidity
version, what will happen is it will raise an error and it will revert all state changes that have happened so far. Now
we can optionally disable this behavior, and to do this, we need to use a special unchecked block. And if we
perform any numeric operation within the unchecked block, then all numeric operation will be performed as they
were performed before. So for example, in this case, if we add two values, they will be equal to 0 and an error will
not be raised. If you read older Solidity code, you may encounter SafeMath library. And this library was
implementing overflow protection for earlier versions of Solidity, but now similar functionality is already
embedded in the language itself, so there is no more need to use this library in newer projects.
Mapping in Solidity
At the moment, our smart contract has one glaring issue. It doesn't track who has already voted. So it allows a
single account to vote as many times as they want. Now to fix this, we need to store who has already voted, and
to do this, we will use a data structure called mapping. Now in other languages, this data structure is called a
dictionary or hash map or hash table, depending on the language that you are familiar with. But regardless of
how it was named, it was the same concept. This data structure allows to store key value pairs and allows to
quickly find a value in this data structure by a key. In our smart contract, it can be a mapping of an address of a
voter to a Boolean where if the value is true it means that an address has already voted. To define the mapping
in Solidity, we need to use the mapping keyword and then in parentheses provide the type of a key, then this
arrow and then the value of a type and then the name of our mapping. So for example, the way we could define
mapping in our smart contract would look like this where we define mapping of address to Boolean, and the
name of our mapping will be hasVoted. Now what operations can we do with the mapping? Well, first of all, we
can store a value by a key. So in this case, the key is Adam, which is a string, and the value is 1, which is integer.
We can also get the value by a key. We can provide a key Adam, and it will return value 1 that we have stored.
What happens if we try to get a value for a non‑existing key? It will return a default value for the value type. So
for example, in this case, the value type is integer and the default value for integer in Solidity is 0. So if we try to
get a value for a non‑existing key, it will return 0. If a value type in the mapping is false, it will return false since
this is a default value for a Boolean type. Solidity defines default values for all types. For example, for an array, it
will be an empty array, and for a string, it will be an empty string. Solidity will also use default values to initialize
variables if we don't initialize them ourselves. Now one question that we need to figure out before we can go and
update our smart contract is how to get an address of a user that has sent a transaction to our smart contract.
Well, to do this, we can use the msg variable, and the msg variable provides information about a transaction that
was sent to our smart contract. So it contains fields like value, which is an amount of ether in wei that was sent to
our smart contract. The sender field, which is an address of a sender of a transaction, and this is the field that we
will use later. And data that contains complete transaction data in the byte format and an identifier of a smart
contract method that was called. The msg variable and its fields are available both in a constructor and in
regular methods.
Using Mappings
So now let's update our smart contract and allow only a single vote per Ethereum account, and we will use a
mapping data structure to track who has already voted. Now the smart contract is in the same state we have left
it in the previous demo. To make the changes, we will first of all need to add another field, and this will be
mapping (address => bool), and we will call it hasVoted. Now instead of recording votes in this function, what I'll
do, I'll first of all add a new function which is called recordVote. And it will receive the same option position as
our vote method, but it will update both the array of votes and the mapping hasVoted. It will write true for the
address of the sender who has sent a transaction to our smart contract. And now all I need to do is to call the
recordVote and provide the option position to it. We also need to add a check that the same account has not
voted already, and to do this, we'll use another require statement. And the condition here will be that the sender
of the transaction has not yet voted. To do this, I get the value from the hasVoted for the address of the sender,
and if it is true, then the require will fail. This will work for the users that have not voted because for an address
that has not voted, it will return a default value which is false, and this is why this require will not fail a
transaction. And I'll also provide an error message here. Okay. So now we can try to compile our smart contract
again, and we can deploy it with the same array of options as before. Let's click Deploy. Our smart contract has
been deployed, and now let's try to vote. If we vote once, everything seems to work fine. But if we vote another
time, this transaction fails because we enforce that the same account cannot vote multiple times. To test how
our smart contract works when multiple users with different addresses use it, we can scroll up here, and we can
specify which one of the test accounts to use. So to use another account, I need to click here, then scroll back
down, and click Vote. And in this case, this other test account will send a transaction. And because this account
has not voted before, this transaction will succeed. If I vote again though, it will fail, so just as we have expected.
Control Structure
In order to implement any nontrivial obligation, sooner or later, you will have to use control structures. And
Solidity provides two major control structures you can find in the majority of other programming languages.
These are if‑else and loops. So let's briefly look at if‑else statements. If you're familiar with other programming
languages, there won't be a lot of surprises for you. To use an if statement, we need to use the if keyword, then
provide the condition of the if block and the body of the if block. And the body will be conditionally executed if
this condition is executed to true. We can also chain multiple conditions using the else if statement. So the block
for the else if statement will be executed if this condition is executed to true and the previous condition was
executed to false. We can optionally also provide the else block that will be executed if all previous if statements
were not executed. One thing to keep in mind is that in Solidity, we can only use Boolean expressions in if
statements. So these expressions are valid in Solidity, but unlike other programming languages like JavaScript, in
Solidity, we cannot use the variables of other types in these expressions. So for example, we cannot use
numbers or strings or arrays in if statements. When working on smart contracts, sooner or later, you will have to
compare strings, and you would imagine that you can just use the = separator to do this. However, this is not
supported in Solidity. So a common pattern to work around this problem is to use hash functions. We can
compute the hash value of our strings we want to compare and then compare the hashes. And if you remember
our discussion about the hash functions, the reason why it works is because the hash function always produces
the same output for the same input. So if strings are the same, their hashes should be the same. Now let's talk
about loops. As in many languages, Solidity provides while loops. To use the while loop, we need to use the while
keyword, provide the condition, and the body of a loop. And this body will be executed as long as this condition
is evaluated to true. Solidity also has the do while loop where the first execution of the block is unconditional,
and then Solidity will execute the body of the loop for as long as this condition evaluates to true. Just as many
other C‑like languages, Solidity has a for loop, and it works exactly the same. It has three sections, all of which are
optional. The first one is to initialize our variables. The second one is to implement a condition for when the loop
should be executed, and the third block is to update variables in our loop. Solidity also supports break and
continue keywords to skip some or all iterations of loop. In this case, for example, if i = equal to true, Solidity will
execute the break statement and exit the loop completely. The continue statement, on the other hand, allows us
to skip a single loop iteration. So in this case, if i = true, Solidity will skip a single iteration and will, in this case, skip
the execution of the push function and will start the execution of the loop from the very beginning.
Structs
In the new implementation of the vote method, we use a loop to find a position of an option by its name. While it
works, it can be quite slow if we have a lot of options, and it can cost us quite a lot of gas because we might
need to check every single option in an array to find the position of an option a user is voting for. What we can
do instead is to use mappings because a mapping is a combination of key‑value pairs, and a key can be the
name of an option, and a value can be its position, and that would be much more efficient. So the way it could
work, we could add another field mapping from string to int. And in our vote method, we can first get a position
of an option by its name using mapping and then implement the rest of the method as before. However, there is
a gotcha with this. So, say if we define this mapping, and a user provides a name of the first option, then our
mapping will return 0. But what will happen if a user will provide a name of a non‑existing option? Our mapping
will also return 0 because 0 is a default value, and a mapping will return default values for non‑existent keys. To
work around this problem, we will use a common pattern that involves using structs, and this is what we're going
to learn next. So struct is a way to define a type where every instance has a collection of fields, and each
instance has the same fields. Structs work like classes, but without methods, so they only contain data. They can
also be nested, so a field and a struct can be another struct. And the other difference between structs and
classes is that structs have no inheritance in Solidity. To define a struct in Solidity, we need to use the following
syntax. First of all, we need to use the keyword struct, then the name of a new type, and in this case, it is User,
and then we can provide a list of fields in the struct. So, in this case, we have two fields, a string name, and an
integer age. To create an instance of a struct, we need to use the name of a struct in parentheses, either provide
just the values of the fields of struct, or we can use this key‑value syntax where we explicitly specify a name of
each field and the value of each field. With structs in Solidity, we can either get values of individual fields. Like in
this case, we get the value of the age field. Or we can set values of fields in a struct, like in this case where we set
a value to the age field. Now when it comes to memory in storage, structs work similarly to arrays. If we have an
array of users in a contract storage like this and if we assign one of these users to a struct in memory and make
any changes to it, this will modify a copy of struct. If we have ever assigned it to User in storage and make any
changes to this variable, this will modify original in the storage of the smart contract. Now, let's go back to our
predicament with default values and see how we can fix it using structs. To solve our problem, we can define a
mapping that looks like this, where the type of a key is still string, but the value is our own struct. And this struct
will contain two fields, a position of an option and the Boolean flag that signals if this option exists, and we'll
always assign it to true for all existing options. Now, how will this help us? If we get an existing option, it will return
a struct where position is assigned to 0, but exists is assigned to true, and this true flag will mean that this option
exists. However, if we try to get a position for a non‑existing option, it will return a struct where all its fields are set
to their default values. So we'll get false for the exists flag, and this is how we can figure out that this option does
not exist.
Using Structs
In the last demo of this course we will reimplement the new vote method, and we will use mappings and structs
to practice our new skills. To update our smart contract, first of all, I'll remove this old vote implementation
because we will not need this, and I'll temporarily add a TODO comment here because later we'll come back
here and reimplement this method. Now to use mappings to reimplement our smart contract we'll first need to
define a new struct, which I'll call OptionPos, and it will have two fields, a position of an option and a Boolean flag
that will signal if this option exists. Now we need to define a mapping just as we've discussed, and it will be a
mapping from string, or a name of an option, to our new struct, and I'll call this mapping posOfOption. But now
we need to initialize our mapping, and to do this, I will add the following for loop. Now in this for loop we first
iterate through the options that the user has provided to us when they deployed the smart contract. And then
for every option we will create an OptionPos struct, and we will initialize it with the current position of an option,
which is i, and exists equal to true. We will then store every OptionPos instance to our mapping, and the key will
be the name of an option, which is options[i], and the value will be our struct instance. Now, having this mapping,
we can go back to the new Vote method and reimplement it using it. And this implementation will heavily rely on
the functions that we have already implemented. First of all, we will check if a voter has already voted, and we
will use the hasVoted mapping to do that. Then we will get a position of an option using the posOfOption
mapping. But before recording the vote, we will check if this option exists, and to do this we will use the exists
flag on the optionPos instance. If this option exists, we will record the vote using the recordVote method that
tracks who has already voted and records a tally of total votes. Now, having this, let's go and compile our smart
contract. And it was compiled successfully, and that was Deploy our smart contract. And I again need to provide
a list of options to initialize it. Now it is deployed, so let's try to interact with it. And I'll provide a string option and
click vote. And as you can see, our transaction was successful. If we get a list of votes, it looks that our vote has
already been recorded. Now let's try to vote for a non‑existing option. And as you can see, this transaction has
been reverted because our smart contract has successfully determined that this option name is invalid.
Summary
I think we've covered quite a lot in this module. We've learned a lot of new Solidity features, and we've used
them to develop smart contracts. We've learned what data structures we can use in Solidity, we've learned
about arrays and mappings, we've learned about control structures, like if and loops, we've learned about
constructors and how we can use them to initialize our smart contracts, and we've also learned about how to
verify input parameters and the state of our smart contracts using assert, require, and revert functions. We've
also learned about the memory in smart contracts, and we've learned about the difference between the
memory, calldata, and storage. Now, up until now, we've only been using Remix IDE, but to become proficient
Solidity developers we need to learn how to use proper development tools. We need to learn how to build smart
contracts, how to test smart contracts, and how to deploy our smart contracts. And to do all of this, in the next
module, we will learn how to use web3 API, an API that allows us to interact with an Ethereum network.
Ethereum API
Introduction
Hi. This is Ivan Mushketyk with Pluralsight, and welcome to the next module of the course Ethereum API. In
previous modules, we were using Remix to develop and deploy smart contracts in Solidity. And while it was easy
to do, it mostly works for learning Solidity. And in this lesson, we will take a step forward, and we will learn about a
more advanced way of interacting with Ethereum. And specifically, we will learn Ethereum API. And this API, or
application programming interface, allows us to write applications that interact with Ethereum programmatically,
and this opens a ton of possibilities for what we can do with the network. In this module, we will have two goals.
First, we will see how we can programmatically deploy a contract using Ethereum API, and then we will learn how
to interact with the deployed smart contract programmatically, and we will practice doing this as well. And now,
without any further ado, let's get started.
Solidity Compiler
Before we can deploy our smart contract or interact with it, we first need to compile it, and this is exactly what
we are going to look into first. We'll first see how we can install a Solidity compiler, then we will learn about what
outputs it produces, and then we'll see how we can compile our smart contract in an upcoming demo. When we
run a Solidity compiler, it generates two outputs. First, it's the bytecode, and this is a set of executable
instructions in the Ethereum bytecode format, and we've covered it briefly in one of the previous modules. And
we will have to use bytecode to deploy our smart contracts, so we'll first have to compile it into bytecode and
then deploy it to the network. The second output is ABI, or application binary interface. In ABI is a file in JSON
format that provides information about what methods our contract has. It specifies the name of the available
methods, their signatures, output types, modifiers, etc. And ABI is used when we want to programmatically
interact with the deployed smart contracts so that various libraries know what methods are available on a
particular contract. To use a compiler on our machine, we first need to install it, and we can do this using the
npm command. We need to install the solc package that contains the compiler, and we also need to use the ‑g
flag to make sure that this compiler is available from any folder on our system. To run a compiler, we need to use
the solcjs command and notice that it is different from the name of the package that contains this compiler. We
then need to specify that we wanted to generate bytecode using the ‑‑bin flag. We then can specify that it
should generate ABI using the ‑‑abi flag. Then provide the paths where our smart contracts are located, and
then provide the name of a file that contains the smart contract that we want to compile. And we can either use
a CLI command to use this solcjs compiler or we can use it as a JavaScript library and interact with it
programmatically.
Ethereum Client
Now, once we've compiled our smart contract, let's see how we can interact with an Ethereum network
programmatically. And to do this, we first need to use an Ethereum client. An Ethereum client is just a running
Ethereum software. Another term that we will use a lot in this course is Ethereum node, and it is simply a
computer running an Ethereum client. And in this lesson, we will learn how we can work with an Ethereum client,
we will also learn about the APIs that Ethereum clients provide, and later we'll see how we can interact with an
Ethereum network programmatically using any Ethereum client. Now let's look at the role of a client in any
Ethereum network. As we mentioned, an Ethereum client is just a running Ethereum software, and each client
stores the Ethereum transaction's history, the blocks that were generated, and it also might store private keys
that the owner of this client can use to sign transactions. A client also provides a web3 API that users can use to
interact with the network. Ethereum network is just a set of interconnected Ethereum clients that are
communicating with each other. And network users can use Ethereum clients to interact with an Ethereum
network using the web3 API. And the name client might be a bit confusing. You might think that if there is a
client, there should be a server that this client interacts with, and there's no specific server in this case. An
Ethereum network is just a network of interconnected equal clients. Depending on the configuration in which
Ethereum software runs on a node, we can identify three different types of Ethereum nodes. The first one is
called a full node, and a full node stores the full blockchain data, and they also verify all blocks that are
generated on the network. So, they propagate correct blocks, and they reject invalid blocks that violate
Ethereum rules. And users can use full nodes to get the current state of the network. For example, they can call
a method on a smart contract using a full node. A second type of a node is called a light node, and they don't
download full blocks; instead, they only download block headers, which contain just a fraction of information of a
full block. They also don't verify blocks, and instead they completely rely on the full node to do this. There is also
another type of node called an archive node. And the way it is different from a full node is that it stores the full
historical data of the network, including very old blocks, and an archive node can be used to process requests
about the historical state of the network. Now, to run your only Ethereum node, you need to run two separate
types of clients. The first one is called an execution client. An execution client listens to new transactions, it
executes transactions, and it stores the network state. And before the major Ethereum update, called The
Merge, it used to be that you only had to run an execution client, but now you also need to run a consensus
client that interacts with the execution client. The consensus client implements what is known as a
Proof‑of‑Stake algorithm, and we will learn about what it does in the next module, but in a nutshell it allows an
Ethereum network to agree on the global network state. These two clients also interact with other
corresponding clients on other nodes via the peer‑to‑peer protocol. When a user wants to access these clients
and use their API, the user usually uses a web3 library in a corresponding programming language, and this library
will connect to both of these clients and use their APIs. Now with Ethereum, there is no single client
implementation. For execution client, the most popular option is Geth, implemented in Go, but there are also
other implementations like Besu, implemented in Java, and so on. The same story with consensus clients. The
most popular client, as of today, is called Prysm, and it is implemented in Go, but there are also other
implementations like Lighthouse, implemented in Rust, and so on. While these clients have different
implementation, they all implement the Ethereum specification that defines how execution and consensus
clients should operate. And having multiple clients is called clients diversity, and it is important because if there
was only one client and this one client had a particular error, this error could be exploited by attackers, and they
could potentially bring down the whole network. Having more clients will make the network more robust against
attacks and errors. So if you want to run your serial node, try to pick up not the most popular clients to promote
the clients diversity.
Web3 API
Now once we've covered what are Ethereum clients and how they work, let's look into what we can do with
them, and namely, what functionality Ethereum API provides. So this API allows us to do quite a lot of things. For
example, we could get a state of a node, check if it is connected to other nodes, etc. Using Ethereum API, we
can also get the state of a network. For example, we can look at the balance of a particular account, we can call a
method on a smart contract, etc. And note that the data will be returned as a particular client sees it. So if it is
still downloading blocks from other peers or if it has been disconnected, it will return stale data. We can also
send transactions using an Ethereum client. So, for example, we can deploy a smart contract, we can send
payments, etc. And Ethereum clients can also store private keys, and when we want to send a transaction, we
have two options. Either we can sign a transaction and send a signed transaction to a client, or we can send an
unsigned transaction to a client and ask it to sign it using a private key that it stores. To interact with the client
and use its functionality, we need to use its API, which is called JSON remote procedure call, or RPC API. And
this API is structured as a number of functions that we can call remotely by sending an input in a JSON format.
So let's go through several of many methods that any Ethereum client provides. The first one is clientVersion,
that simply allows us to get a version of a client that we interact with. The second method is called peerCount,
and it allows us to get a number of peer nodes that our node is connected to. Other methods are
getBlockByHash and getBlockedByNumber, and these methods allow us to get data about a particular block,
either by number or by its hash. And the last method that we will cover is called sendTransaction, and this
method allows us to send a transaction to the Ethereum network to deploy a smart contract, send a payment,
etc. And there are many other methods, and you can read Ethereum documentation to get a full list of methods
in its API. Now let's look at how we can send a JSON‑RPC request to a client if we want to do it manually. As an
input to an RPC request, we need to provide a JSON object that has several fields. The first one is a version of
the API. The second is a method that we want to call, and, in this case, we'll call getBlockByNumber method.
Then we need to provide parameters for the method call, and the list of parameters depends on what method
we want to call. For the BlockByNumber, we need to provide a number of a block we want to get and the flag
that specifies if we want a full transaction object or just its hash. And the last parameter is an id, and this is used
by a caller to match a request with a reply. A reply will also contain an id, and a client will be able to find out
which request this reply is for. And a reply will also contain an id, and the caller will be able to figure out which
request this reply is for. Now let's look at the response data. The first field is an id that we've just talked about,
the second one is a jsonrpc version, and the third one is result, and the result will depend on what methods we
have called. So, in this case, because we try to get information about the block, the result will contain data of a
block that we have requested.
Geth Client
Now let's briefly talk about how we can use one of the most popular Ethereum clients called Geth. The exact
instructions for how to install Geth depends on what operation system you're using. If you're using macOS, you
would need to first unload a repository that contains Geth using brew, and then you would need to use the brew
install to install Geth. If you use Ubuntu, you would need to use apt‑get to install Geth. And for Windows, you
would need to download binaries or build Geth from the source code. Regardless of how you install it, you then
need to run Geth client. And to do this, you need to use the geth command and provide a few parameters. If you
want to use Geth with Goerli network, you would need to use the ‑‑goerli flag. And if we use it, our client will
connect to go Goerli. If you want to use the JSON RPC API, we would also need to provide the ‑‑http flag that
will enable the HTTP‑RPC server on Geth. We can also specify in what mode we want to run our Geth client. If
we set ‑‑syncmode flag to a light, Geth will run as a light node. And if you recall, we've discussed earlier that it
means that our node will rely on other nodes to validate blocks. But we can also run it as a full node. But, in this
case, it will take longer for Geth to download all the blocks, and it will require more resources, more network
bandwidth, more disk space, more CPU, etc. And the final important flag in Geth is the ‑‑http.api. Geth
functionality is separated into different APIs, and here we can specify what APIs will be available over HTTP. So
this is necessary to restrict what outside users can do with our client and would allow to get some extra security.
And if we run this command, it will start Geth on port 8545, and we can also configure this port. And once we
have Geth up and running, we can use a library to connect to Geth and interact with the Ethereum network
programmatically. And we will see how to do this in just a few minutes, but there are also a few more ways to
interact with Geth. One way is to use the Geth CLI command, and we can use one of its subcommands to
interact with it. For example, if we use geth accounts, it will allow us to manage Ethereum accounts on the Geth
client. If we run geth init, it will create a new genesis block and it is needed if you want to start your own
Ethereum network, for example, your own test network. We can use geth help to display help. And we can also
use geth attach to connect to interactive JavaScript session running on Geth client. And these are not all of the
commands supported by Geth. If you want to get a full list, you can go to the Geth documentation. Now, if
you've used geth attach command, you can use its JavaScript console, and we can use one of the following
objects in this console to interact with Geth. For example, the admin object allows us to control the client node.
Personal object allows us to manage Ethereum accounts on this node. Txpool object provides information about
transactions that can be executed on the node. And the last one is debug, that you are unlikely to use often, and
this object allows us to debug Geth itself. And here's an example of how we can use this JavaScript console. We
can use the attach command, and once we execute it, we can use some of these JavaScript objects to interact
with Geth, like admin peers to get information about peer nodes Geth is connected to, or we can look at this
transaction pool that contains transactions that are not yet executed that are also called pending transactions.
And using the status field of this object, we can look at the status of the Geth transaction pool. In this module,
we are not going to run in Ethereum node. Instead, to focus on learning Web3API, we will use one of the
Ethereum node providers. And a node provider is a service that runs an Ethereum node for us, so we don't need
to install it or manage the node ourselves. And we can even use some of them for free if we don't send a lot of
transactions, like in the demos of this course. There are a few providers to choose from. One is Infura, which we
will use in this course. And this is one of the most well‑known providers, and it is also used by MetaMask. So
when we use Metamask, it is using Infura to interact with an Ethereum network. But there are also other
providers like Alchemy. And if you recall, we use their website to get the Ether. And there are also many other
providers. If we use one of these services, they will give us a URL that we can use to send requests to. And once
we have a URL from one of these services, we can just use it as a URL of our own Ethereum node. And there is
no difference from a developer perspective if this URL is pointing to a locally running client or to a client that is
operated by a node provider.
Promises in JavaScript
In the upcoming demos, we will write a lot of asynchronous code, and asynchronous means that when we
perform an operation we start a long running task, and our code will be notified when it is finished or receives an
error. And this will include things like sending transactions to an Ethereum network, calling methods, etc. And to
write this asynchronous code, we will use modern JavaScript idioms that on the one hand are easier to read and
write, but also requires you to know them. And in this clip, we will briefly learn these idioms. And if you're already
proficient with JavaScript, then feel free to skip this particular clip. We won't be covering anything related to a
Ethereum here. We will cover two main concepts, promises and async/await. So if you are familiar with these, feel
free to skip this next clip, and if you're not familiar or unsure, I would encourage you to keep watching. So, let's
start with a simple example of an asynchronous operation, and we will talk about the hypothetical method that
would look like methods that you can find in older JavaScript libraries. So let's say that we have a hypothetical
method that can send an HTTP GET request, and this method has three parameters, a URL to which we want to
send the request, the onSuccessFunc, which is a function that will be called by the get method if a request
succeeds, and the onFailureFunc that will be called if this request fails. And these functions that are called when
an operation is finished, they are are also called callback functions. And here is an example of how we'd use this
function. To call this function we would first specify a URL where we want to send this request, then define a
function that will be called if a request succeeds, and this function receives the only parameter, which is a result
of an HTTP request, and then also define a function that will be called if a request fails, and this function receives
a single parameter, which is an error that has occurred. And then we can pass these functions to the get call.
Now, this would work, but if we want to send another request if we receive a response from the first request,
we'll then need to define another onSuccess function and another onFailure function. And this can quickly get
out of hand. We would get these nasty nested callbacks, and the situation sometimes is referred to as a callback
hell. Now, fortunately in modern JavaScript, we have a better way to work with asynchronous code, that is called
promises. And if our hypothetical library would be implemented with promises, we'd have the following interface.
It would now accept a single parameter, which is a URL, where to send the request, and when we call it, it would
return a promise object. And to get the result data from this promise object, we would need to call the then
function on it and provide a callback function that will be called if this request succeeds. We would also need to
provide another callback using the catch function that will be called if this request fails. Now, once we've seen
an example of a promise, let's talk about the characteristics. A promise represents an asynchronous operation. If
you have a promise, it may not have a result yet, but it will notify when it has it. There is no way to get a result
from a promise directly. To get the result from a promise we need to use the then method that receives a
callback that will be called if the operation succeeds, and the catch method receives a callback that will be
called if an asynchronous operation fails. And the benefit of using promises is that with promises we can perform
multiple requests in the row and link them together. So for example, if after the first request I want to send
another one, then in the then callback we can just return another promise for the operation that sends the
second request. And then we can again call the then method that will receive the result of the second request
and so on. And in addition to this chain of operations, we can also provide a catch callback that will be called if
any of these operations in this chain fail. But in modern JavaScript there is even a better way to work with
asynchronous code, called async/await. If a function returns a promise, instead of using the then method to
handle a result we can use the await keyword, which will allow us to sort of extract the result from promise, and
then we can use the result value to send another request and so on. So using these await keywords, we can
write code that works with asynchronous operations as if we are writing a normal known asynchronous code.
And behind the scenes JavaScript will transform this code into using promises. So, all of this is just a very nice
syntax sugar. And the best part of this is that it even works with regular try catch blocks. So if any of these
asynchronous operations throws an exception, then the catch block will be executed to process it. One thing to
note is that this only works in functions that have a special async keyword in their definition. And if we call this
function anywhere else, this function will return a promise, and to handle this promise we can either use another
async await or we can use then catch methods.
web3.js
To use web3 API we need to use one of the existing web3 API libraries that are available for various
programming languages. In this course, since we are going to use JavaScript, we will use the web3.js library, but
there are also libraries like web3j for Java, web3.py for Python, or Nethereum for the .NET platform. To use the
web3 library, we first need to import it in our JavaScript code and then create an instance of web3 type. After
this, we need to specify how to connect to an Ethereum client. And to do this, we first need to create an
instance of a web3 provider, which specifies how to connect to a particular client, and then set it to web3 using
the setProvider method. And in this case we use the HttpProvider that will connect to an Ethereum client using
the HTTP protocol, and we will use the following URL. Once we have a web3 instance configured, we then can
use it to access an existing smart contract. And to do this, we first of all need to get an abi of a contract we want
to interact with. Once we have it, we need to provide a contractAddress. And then having those, we can create
an instance of a contract type from the web3 library and provide an abi and the contractAddress. When we want
to send a transaction using the contract instance, it will return a promise that we can then use to receive a result
of our transaction. Behind the scenes, the web3 library will send the request to an Ethereum client, and then this
Ethereum client will send a transaction to an Ethereum network. Once this transaction is executed, our promise
will be updated, and it will either call the then or the catch callbacks. So here's how we can send a transaction
using web3.js. So once we have our contract instance we then need to specify what method to use. And to do
this we need to use the methods field and then specify the name of the method we want to call and provide
arguments that we want to pass in a transaction to this method. And this by itself won't send a transaction to
Ethereum, we need also to call the send method on it that will then interact with an Ethereum client to send a
transaction to the network. And send will return an instance of a promise, so we need to use the send method to
get the result of our transaction. And if the transaction succeeds, web3.js will provide an instance of a received
object that contains information about an executed transaction. Now, as with any other JavaScript code, we can
also use async await when a method returns a promise. So, instead of using then, we can use the await keyword
to extract and receive from a send method result. Now let's go through a few more scenarios of how we can
interact with a smart contract using web3.js. If we have an overloaded method, which is when we have two or
more methods with the same name but different parameters, then we'd need to use a slightly different way of
calling it. We need to first of all get a method by providing its signature, like this, and then provide arguments
that we need to pass to this method call. And then again we need to use send to send a transaction and then to
be notified when it is executed. We can also deploy a smart contract using web3.js. To do this we first of all need
to create an instance of a Contract type, provide an abi of our smart contract, and then we need to call the
deploy method, and we need to provide the bytecode of the smart contract that we want to deploy. And then
this, again, won't send a transaction to deploy a smart contract. We, again, need to use the send method to send
a transaction to an Ethereum network. And as a result of this call, we will get an instance of a smart contract from
the web3.js library that we can then you to interact with it or, for example, to get its address. And we can also use
web3.js not just to send transactions, but we can also use the call mechanism to call a method. And using call
will look almost exactly the same, but instead of using the send method to send a transaction, we will call the call
method instead. And in this course, we won't cover all of the features of web3.js, but if you want to learn more
about this library, I'll suggest to go to the official documentation on this website.
Account Nonce
Now we will talk about an advanced topic related to using the web3 API, and specifically we will talk about an
account nonce. And an account nonce is just a unique ID of a transaction from a particular Ethereum account.
For the first transaction a nonce is set to 0, for the second it is set to 1, and so on. And the point of the nonce is
that it prevents a double spend. It prevents another user from resending your transaction so it can be executed
twice. And this is because if we want to send a new transaction, we need to send a new nonce, the next number,
and we would have to sign this new transaction, and only the owner of a private key can do this. And an account
nonce can be a confusing topic. It can be a source of many issues if used incorrectly, and it was handled for us
by the Ethereum software so far, so we didn't have to deal with it, but it might become an issue once you start
working with more complex applications. And the way it works is an Ethereum network stores the next expected
nonce for every Ethereum account, and when it receives a new transaction it looks at the nonce of these
transactions, and it decides what to do with it using the following algorithm. So it looks at the nonce, and the
nonce of a transaction is what it expects, then this transaction is executed. If it is higher than expected, then the
network will wait until it gets a transaction with the expected nonce. And then if it is lower than expected, one of
two things can happen. First of all, if a transaction with this nonce is still pending, then the new transaction can
replace the pending transaction, but only if it has a higher gas price. And if a transaction with this nonce has
already been executed, then the network will return an error. And here is an example of what can happen if we
skip a nonce value. So if we have a new account, we first create a transaction with nonce 0, and it is executed. If
we then send a transaction with nonce 1, it is executed as well. But if we skip a nonce in the next transaction and
set it to 3 instead of 2, then this transaction will be pending until the network processes a transaction with nonce
2. Now, using Ethereum API we can manually set the nonce, and we can also get an expected nonce from the
network. To get the expected nonce we need to use the getTransactionCount method and specify the address
of an account for which we want to get the nonce. And then we can also specify the nonce when we send a
transaction. Now, so far it seems that managing a nonce in Ethereum should be pretty straightforward, but it
might become quite complicated. First, the getTransactionCount method may return an outdated nonce value.
And if you have multiple machines sending transactions to the Ethereum network, if they use
getTransactionCount, it might return the same values for multiple callers. So, a solution for this problem is to
keep track of a nonce per account in your local database and ensure that every time we send a transaction a
nonce in the database is incremented so that every generated transaction will have a unique nonce.
Summary
In this module, we've covered a lot of information. First of all, we've learned how we can use an Ethereum
compiler and we've learned that a compiler produces two types of outputs, first a bytecode of a smart contract
and an API that contains information about the methods that the contract has. We've also learned about what is
an Ethereum client, and an Ethereum client is just an Ethereum software running on any Ethereum node. A
client stores transactions history of a network and it also might have private keys that can be used to sign
transactions. Ethereum clients also provide web3 API that users can use to interact with the network. We also
learned about what methods are provided by a web3 API and we've learned that we can use it to get
information about the network, send transactions to it, deploy contracts, etc. And we also saw how we can use a
web3.js library to use this API. We've also learned about the concept of an account nonce, and an account
nonce is a special mechanism that allows to protect from a malicious user that might try to resend the same
transaction twice. And while nonce has been managed for us by the Ethereum software in this course, we've
covered that it might become a source of problems if we set it incorrectly.
Forks
Before we talk about how blocks are generated in Ethereum, we need to talk about another concept called
forks. So far, when we were discussing blockchain, we were always drawing a single chain of blocks, but what
could happen is that different users can temporarily disagree what is the current state of the network and can
start building parallel chains, assuming that what they have is the most recent network state, and those different
chains are called forks. So forks are temporary disagreements between peers, and if a fork occurs, different users
can see different, and even contradictory states, and this is all caused by the decentralized nature of Ethereum.
Because there is no centralized master, every actor needs to make decisions on what is the current state on
their own. But eventually those forks should be resolved by the Ethereum protocol. Eventually we should have
an agreement between peers what is the current state of the network. Just to simplify things a little bit, the way
a protocol decides what is the current chain is by selecting the longest chain of blocks, and then users,
assuming that the longest chain is the current chain, they start generating new blocks on top of the current
chain. So why is this all important? Because this can cause a so‑called double spend attack. And here is how it
might work. Let's say you want to buy pizza with Ethereum. So you've paid for your pizza, you got your pizza, you
ate it, and then you decide to trick everyone and generate a longest chain, which surprisingly does not include a
transaction where you've sent the payment, and from a merchant's perspective who sold you this pizza, the
payment was reversed. They saw it on one network state and then with your longest chain it has disappeared.
Now in the current history your payment is reversed and now you can spend your Ether again and get another
free pizza. So here is a more visual way of representing this. First you send a new transaction, and a new block is
generated that contains your payment. And then you generate a bunch of new blocks, and because this is now a
longer chain, this becomes the current state of the network. And your new chain is almost the same as the old
one with the only difference that your payment is missing there, so you still own your Ether in the new chain.
Now Ethereum would be pretty useless, however, if this trick was easy to do. If anyone could add blocks at any
time they wanted, the network would not be secure. So the way to solve this problem is that we need to have a
leader that could generate new blocks. But we can't just select one permanent leader since this would ruin the
decentralization benefits of Ethereum. So the way it works is the network gives a chance to anyone to become a
leader and generate a new block. And there are two different algorithms that make this happen, and we've
already mentioned both of them. The first one is proof‑of‑work, and in proof‑of‑work to generate a new block, a
node would need to solve a complex mathematical puzzle, and to solve this puzzle we'll need to use a lot of
computational resources. The more resources someone has, the more chance this user has to generate a new
block. Proof‑of‑work is still used by some other blockchains like Bitcoin, but Ethereum now uses proof‑of‑stake,
and with proof‑of‑stake to become a leader, the user needs to put some Ether at stake, and if they try to cheat,
this Ether is then confiscated. With the proof‑of‑stake, the more Ether someone stakes, the higher the
probability that they will be able to generate a new block.
Proof of Work
Now let's talk about how a proof‑of‑work algorithm works. While Ethereum doesn't use proof‑of‑work anymore,
I've decided to include this video for two reasons. First of all, it is used by other blockchains, and second, it is
interesting to compare how proof‑of‑stake differs from proof‑of‑work. On every node that is trying to generate
blocks, there is a pool of incoming Ethereum transactions. Periodically, some of these transactions will be
selected to be included in the next block, which is called a candidate block, and this candidate block will include
transactions from the pool, a hash of the previous block, and a timestamp of a block generation. And then, when
a block is created, a participant will calculate a hash value of this block. If it is less than a special network
parameter called difficulty, that we will denote as D, then the next block is generated, and it will be sent to the
network. If however, a hash is greater than the difficulty, then the miner repeats the process and tries to create a
new block with the hash value that is less than D. And to do this, a miner has another parameter in the block
called nonce. And by changing this nonce a miner can try to create a block with the expected hash value. And
this is exactly a mathematical puzzle that miners are trying to solve. So the way it works, miners start with a block
with nonce equal to 0, and if a hash is greater than the difficulty, a miner tries another nonce. And since even a
tiny change completely changes the hash value, they increment the nonce and compare it with the difficulty
again. And this goes on and on until eventually they find a nonce which generates a block with the expected
hash or some other node generates another block, in which case the process starts all over again. And this
process is called mining. A node tries different nonces until one of them gives the required hash. It might be
confusing though when we say nonce because there are two types of nonces in Ethereum. One is a transaction
level nonce, and this is a nonce that we've discussed before. This is just a number of transactions sent from a
specific account. What we are talking about here is a block nonce that miners use to change a hash of a block.
With proof‑of‑work. once a block is generated, a miner sends this block to other peers. These peers validate if a
puzzle was solved. And it is very easy to validate because all they need to do is to compute a hash of a block and
compare it to its difficulty. And calculating a hash value is very fast. If the block is correct, they store it locally in
their storage and send it to other peers. If it is incorrect, they just reject it right away. And once we have a new
block the next blocks will be built on top of this newly mined block. Ethereum mining is a randomized process.
Any node, just by pure luck, can generate a block that has a required hash, but the probability of this is
extremely small. So to increase chances in proof‑of‑work to generate a correct block, a user might get more
machines that will calculate hashes of different versions of the same block in parallel, or they can buy better,
more expensive, more powerful equipment. In the case of Ethereum, the mining was done with powerful graphic
cards. In the case of Bitcoin, for example, it is done with specialized hardware that is only designed to calculate
hashes. And usually mining is not done by individuals, it's individuals and their mining pools that are using
collective resources to generate blocks. Now, difficulty in the network is not a fixed parameter, and it was
automatically adjusted. If more users try to mine blocks, they would find a new block faster just because they
use more resources, but Ethereum network was calibrating the difficulty to make it easier or harder to ensure
that new blocks are generated roughly every 15 seconds. But why would users go through all of this? Why
would they buy expensive hardware and spend electricity? This is because whoever generates a new block will,
first of all, get a fixed amount of Ether for a mined block, and second of all, they will get a portion of fees from all
executed transactions. And we will cover how fees work later in this module. Now one thing to note is that with
proof‑of‑work, a miner can still reverse a transaction. All they need to do is just generate a longer chain than the
existing chain. They would need to pick a block in the past and start mining blocks by themselves to generate
their own chain, and other users would switch automatically to their longer chain. However, to do this they had
to get more than half of all mining resources used by all the Ethereum users, and this is why it was called a 51%
attack. But since there were a lot of people mining Ethereum, an attack like this would have been extremely
expensive.
Proof of Stake
While proof‑of‑work was used for many years, and it is still used by some other blockchains, it has a few glaring
issues. First of all, it requires a lot of energy. Proof‑of‑work through Ethereum was consuming roughly the same
amount of energy that is used by the whole country of Kazakhstan, and this computation had a carbon footprint
of Sweden. This is a lot of used energy. And the second issue was the possibility that someone could perform a
51% attack. As we've discussed before, it can be performed by someone who has more than 50% of all total
mining resources of the network. While it is very expensive to collect that amount of equipment. The worst thing
is that if this happens, the attacker could repeat this again and again and again, because they retain equipment
and can perform an attack again. Now to put this into perspective, a single Ethereum transaction was consuming
500 kW of energy, which is the same amount of energy that is needed to perform 266,000 Visa transactions. To
solve this, Ethereum developers for a very long time were working on an alternative consensus algorithm called
proof‑of‑stake. In this algorithm, users can become block validators. Each validator can be selected to propose a
new block or vote on new blocks. There is no mining anymore, no computational puzzles to solve, and no need
for miners. To become a validator, a user needs to stake some amount of Ether, which they can't use in the
meantime, and if they misbehave, this stake will be taken from them. But why would anyone become a validator
in the first place? Well, again, because, just as with miners, a network rewards the validators. Just as with
proof‑of‑work, a validator gets a part of transaction fees and they get a block reward, but with proof‑of‑stake it is
smaller than a block reward for proof‑of‑work. To become a validator, a user can either become an individual
validator, to do this, they would need to stake at least 32 Ether, or they can join a staking pool where users can
combine their Ether to create a bigger stake and then share rewards among themselves. The more Ether
somebody stakes, the higher the chance that they will be selected to validate a new block and get a reward.
Now, Ethereum has migrated from proof‑of‑work to proof‑of‑stake in 2022 after a long migration process.
Ethereum started with proof‑of‑work in 2015, and it is a much more battle‑tested algorithm than proof‑of‑stake.
Then in 2020, Ethereum developers created a so‑called Beacon Chain that was running in parallel to the
Ethereum Mainnet, and in 2022, Ethereum switched to the new Beacon Chain that is using the proof‑of‑stake
algorithm. And this is not the end of the major Ethereum updates, and we can expect more changes coming
later. And all these changes are sometimes branded under the name Ethereum 2.0. It includes the proof‑of‑stake
algorithm that we've just discussed, but it also includes future changes, such as rollups, that will allow to move
some transaction execution from the chain, and sharding, that will allow to split a single network into multiple
chains working in parallel.
Summary
In this module, we've looked under the hood of how Ethereum works. And we first of all started with forks, which
are disagreements in the current network state. We discussed issues with forks, as well as some catastrophic
consequences if we allowed anyone to create new blocks at any time. To allow an Ethereum network to agree on
a single stage, Ethereum uses a consensus algorithm. Previously, Ethereum was using an algorithm called proof
of work. In this algorithm, to generate a new block, a minor had to solve a complex mathematical puzzle. This
algorithm has a few downsides. For example, it requires a lot of energy and a lot of specialized hardware. The
new algorithm used by Ethereum is called proof of stake. And to generate blocks with proof of stake, a user
needs to stake some ether that can be confiscated if users try to cheat. And the benefits of this algorithm is that
it is much more energy efficient, and it doesn't require any specialized hardware. We've also discussed how fees
work in Ethereum, and we've discussed that in the current model, the fee consists of the base fee and a tip. The
base fee is calculated automatically based on the network usage, while the tip is selected by a user. The base
fee is burned on every transaction, while the tip is collected by a validator.
Truffle Framework
Introduction
Hi. This is Ivan Mushketyk with Pluralsight, and welcome to the next module of this course, Truffle Framework. So
far in this course, we were using Web3 GS to deploy our smart contract and interact with an Ethereum network.
And while this is a powerful tool to automate operations with the network, it is quite a lot of work to use it. It is
just too low level. What we need to become really productive, we need a proper development environment that
will allow us to implement and abstract contract development and deployment. And this is exactly what we will
learn in this module. We will learn Truffle framework, which is, as of today, the most popular development
environment for Ethereum. And we'll create a project for the voting smart contract using Truffle framework. Now
let's talk about what skills are we going to learn in this module. First of all, we will learn how to create a
development environment to work on smart contracts. We'll see how we can deploy smart contracts using
Truffle framework. We will write unit tests for our contract, and we'll learn how we can test our smart contracts.
We'll see how we can run a local test Ethereum blockchain that we'll use to test our smart contract. And we will
learn how we can debug smart contracts using Truffle. Now, without any further delay, let's get started.
directory in our system. To execute Truffle, we need to run truffle and specify what truffle command we want to
execute. Now let's look at the main commands we can use with Truffle. First of all, it's truffle init, which creates an
empty project that we can use to start working on our smart contract. We can also download a pre‑existing
demo project using the unbox command, and you can download it to see how it is implemented, how tests are
written, etc. A good project to start is called metacoin, which is an example of a Truffle project that implements a
simple cryptocurrency on top of Ethereum. If you run truffle compile, it will compile all smart contracts in the
project, and if you want to run a test Ethereum environment, you can run the truffle develop command. To run all
unit tests, we can use the truffle test command. And once our smart contract is ready, we can run the truffle
migrate to deploy our smart contract. Now, all truffle projects should be organized using a certain folder
structure, and here is how it looks. The first folder is called contracts, and this is where we should put source
code for our smart contracts. The next folder is called migration, and it contains migration scripts, or we could
also call them deployment scripts. These scripts define the logic for how to deploy your smart contracts to an
Ethereum network. The next folder is called test, and it contains all unit tests for smart contracts in this project.
And these tests can be implemented either using JavaScript or Solidity. And at the root folder there should be a
configuration file for the project, and it should either be named truffle.js, or it can have an alternative name
called truffle‑config.js.
Contracts Migration
Now, once we've covered the basics of Truffle, let's look at how we can simplify the deployment of our smart
contracts. And the process of deploying smart contracts in Truffle is called a migration, and it works quite
similarly to data migration in the database world. As with database migration, we need to break down the
deployment of our smart contract into several steps, and each one of these steps can deploy a smart contract
or multiple contracts, or it can interact with them. And just as with state base migrations, each step is executed
only once. Now, to keep track of the current state of a migration, Truffle deploys a separate smart contract that
stores the number of the last executed step. Now let's look at an example of a migration step. First of all, in a
migration step we need to import an object for our smart contract that we want to deploy, and to do this, we
need to use the artifacts.require function and specify the name of the file with our smart contract. Then we need
to define a function that will implement a migration step, and we need to export it, and to export it, we need to
assign it to the module.exports field. Now this function that we define accepts a parameter called deployer. This
is an object that we can use to deploy a smart contract, and to deploy the smart contract, we need to use the
deploy method on the deployer, provide the smart contract we want to deploy, and optionally, we can provide
arguments that we want to pass to the constructor of our smart contract. And since this is an asynchronous
operation, we need to use the await keyword to wait until the deployment is finished. To interact with a deployed
smart contract, we first of all need to get an instance of the deployed smart contract using that deployed
method, and then we can call methods on our smart contract. And notice that the way we call methods on the
smart contract in Truffle is much more compact than the one we're used to with web3.js. And this is because
Truffle contains a truffle‑contract API, a more high‑level API to interact with smart contracts. If you recall, to use
web3 you would have to get the methods object, then provide the arguments for this method, and then call the
send method to actually send a transaction. Now with truffle‑contract, all we need to do is to call a method on a
contract object, provide arguments, and we can also provide additional parameters, such as what account to
use. Now when we know how a single migration step looks like a truffle, let's look at a bigger picture. In a
migration folder, we need to provide a list of steps for our migration, and the name of each file should contain a
number and a name of a migration step. Now, the name is just for human readability, and number in front of the
name is used to order migration steps. If we run truffle‑migrate, it will execute each step only once, and it will
execute them in the order of numbers at the beginning of the name of each file. Now, if after this we run truffle
migrate again, then Truffle will do nothing because it has already executed both migration steps. Now, if we want
to make additional changes, first of all, we need to create a new migration step, and if we run truffle migrate after
this, it will only execute the new migration step.
Multiple Networks
It is very likely that a single Truffle project will have to work with multiple Ethereum networks throughout its
lifecycle. Eventually, it can be deployed to the main network where Ethereum users could use it, but it could also
be deployed to a test network like Goerli for testing. Or it also can be deployed on a test environment which just
emulates an Ethereum network. Now to connect to different networks, we need to specify configuration
parameters like hostname and port of an Ethereum client that we need to use for each network. And this is
exactly what we can define in the truffle‑config.js file. So let's see how we can do this. Truffle js or truffle‑config.js
files contain an object that represents a Truffle configuration for a particular project. And in this object, there is a
network object property that defines configuration for different networks. Now, if we want to specify
configuration for a particular network, in this networks object, we need to create a new field. And in this field, we
can specify parameters for how to connect to an Ethereum client that has access to this network. So for
example, we can specify a hostname of an Ethereum client. We can specify a port that we should use to connect
to this client. Next, we need to specify an ID of an network that we want to connect to. And network ID is a new
concept for us that we did not discuss before. Every network on Ethereum has a network ID. For example, the
main network has an ID of 1, and we can also specify an address of an account that we should use to deploy
contracts to this network. Once we have multiple networks, we can specify what network we want to deploy a
smart contract to. If we run truffle migrate without any additional arguments, by default, it will deploy to the
network that's named development, but we can also change it and deploy a smart contract to a different
network. And to specify what network we want to deploy our smart contract to, we need to use the network
argument and specify the name of the network. And by the way, this name is the same name as the name of the
field in the truffle configuration file. When we are writing code for smart contracts migration, we can also
implement network‑specific large in migration steps. To do this, we can use the second parameter to the
migration function that is called network, and this parameter contains the name of a network where this
migration step is deploying our smart contract. Talking about migrations, we can also restart a migration. What it
does, it ignores the current state of the smart contract and allows us to go through the deployment steps from
scratch. And to do this, we need to run truffle migrate with the reset flag. If we want to reset a deployment for a
particular network, we can again provide the network name using the network argument.
Test Environments
So far in this course, when we wanted to test our smart contracts, we were either using Remix with its embedded
test environment or we were using Goerli, an Ethereum test network. But none of these methods were very
convenient, especially with the Goerli network, where we had to have an account with Ether, deploy our smart
contracts, wait, etc. But with existing Truffle tools, it is easy to create a test environment for Ethereum. This test
environment will be just a single process running on our machine and it will implement the same web3 API as the
regular Ethereum client implements. And these test environments are very convenient. They will emulate a
whole network with multiple accounts and fake Ether. Now when it comes to selecting a test environment, there
are two main options. The first one is Truffle Develop, which is bundled with Truffle, and the second one is
Ganache, which is a separate application that we'll have to install separately. Now both of them have the same
purpose. They emulate an Ethereum network and they allow us to test our web3 code or our smart contracts.
Now, the biggest difference is that Truffle Develop is a CLI‑only tool, while Ganache supports both CLI and UI.
And in this course, we will be using Ganache. We will deploy our smart contracts to Ganache, and we will
interact with Ganache instead of interacting with a test network. Now if you want to use Truffle development
environment, we need to run truffle development command and it will start an interactive console for the Truffle
development environment. If you want to use Ganache instead, you will have to download it and install it, and we
will do it in just the next demo.
Running Ganache
In this demo, we will install and start a testing environment using Ganache, and then we will explore its UI, and in
the next demo, we will use Ganache to test our smart contract. To install Ganache, you need to go to this
website, trufflesuite.com/ganache and click on the Download button. And here, I can download Ganache for
macOS because this is the OS that I'm using, but you will see a download button for your operating system. So
now I've downloaded Ganache, and I can install it as a regular application. And now, once I have Ganache
installed, I can start it. Once Ganache is started, it asks me to create a workspace, and a workspace is like a
project that we'll work with at Ganache. It has two options, a quickstart, so just to start a test blockchain on
Ganache, or it has an option to create a new workspace, and this allows us to link a Ganache workspace with a
Truffle project. Since we don't have a Truffle project just yet, I will just go with a quickstart option. And just as a
note, Ganache supports both Corda and Ethereum block chains, but we will use Ethereum. Now, this is how the
Ganache interface looks like, and here we have 10 test accounts, and each one of them has 100 E's. And we're
going to use these test accounts when we interact with our test network. We also have some additional
information here about our test network. So first of all we have the current block number, which is 0 because no
new blocks have been mined, we have a gas price and a gas limit per block, and we have a network ID, and we've
discussed that every Ethereum network has an ID, and this test Ethereum network has this ID, 5777. We also
have a URL that we would need to provide to web3.js if we want a web3.js code to connect to this test network.
And down below, we have a mnemonic for the private key for this account, and we've seen this already before
when we were creating an account with MetaMask, and this is the same concept, this is just a way for Ganache
to show the private key for this account. Now, another interesting field in this interface is called HD Pass, and we
did not discuss this before, but just to give you a very quick overview, this field defines how to create multiple
accounts from a single private key. And, this goes beyond the scope of this course, but the idea is that we can
back up just a single private key, and then from this private key we can create multiple Ethereum accounts. And,
if you want to know more, I will suggest to google for BIP 32 and BIP 44, and BIP stands for Bitcoin Improvement
Proposal, and this proposal has been implemented for both Ethereum and Bitcoin. Now let's explore the rest of
the interface of Ganache. First of all, we have a list of blocks that have been mined in Ganache and we only have
the first block with number 0, which is our genesis block. We also have a list of transactions that have been
executed with Ganache, and as we can see, at the moment we have no transactions. We also have a list of
contracts, and if we deploy any contracts to this test network, we'll see here all the contracts that have been
deployed. But, what we would need to do is to link a Truffle project with this Ganache workspace, and we will see
how we can do this in just the next demo. The next up here is events, and we have not covered this in this
course yet, but, just to give you a brief overview, events is a mechanism that allows us to store additional
information about a transaction execution, and we will see how we can use them in one of the next modules.
And the last step is called logs, and it just contains debug logs for Ganache that we can use to troubleshoot
Ganache. Okay, now when we had an overview of the Ganache interface, let's see how we can use it to test our
smart contract.
Errors Checking
So far, we were only writing tests that we're sending valid arguments to our smart contract, and we never had
the case when calling a method would result in an error. But in this demo, we will add additional unit tests that
cover the error cases of our smart contract. First, let's see what happens if we get an error from our smart
contract. And to trigger an error, all we need to do is to try to vote twice from the same account. Because if you
recall, our smart contract only allows one vote from one account. Now let's try to run our tests and see what will
happen. As you would expect, the test execution has failed. And we got this error that there was a VM Exception
while processing transaction, and we have an error message, which is Already Voted. And notice that this is the
same error message that we return in this line if the same account tries to vote twice. So our contract has the
right behavior, but how do we write a test for it? And to test this, we'll have to change our test in the following
way. First of all, we'll have to wrap our test in try catch block because if a smart contract reverts a transaction
execution, Truffle contract API will throw an exception. And we catch this exception, and we check that the error
message is the same error message we return in our smart contract which has already voted. Now the last thing
that we need to do is we need to check that exception was actually thrown. And to do this, we called the
expect.fail method that if executed will fail the test right away. And this is exactly what we want. If we manage to
get to this line, it means that our smart contracts have not reverted a transaction, which means that we have a
bug, and this test should fail. Now let's try to run our tests again. And as you can see now, all of our tests are
passing. Just for completeness, we will add another test on this module that will check if two different accounts
can cast two votes. And this test will look almost exactly like the one we had before with the only difference that
now we get two addresses. And we sent the first transaction from the first address and the second transaction
from the second address. And we expect that both votes will be recorded. Let's run our test again. And now we
have all five tests passing just as we expected.
Summary
In this module, we've learned Truffle, an important tool for smart contract development. We saw that we can use
it to implement a comprehensive development environment. With Truffle, we can build our smart contracts, we
can test our smart contracts, and when they're ready, we can deploy our smart contracts to a test or to a real
Ethereum network, and we can also debug our smart contracts using either Truffle CLI debugger or using Visual
Studio Code plugin. To use Truffle, we need to organize our project using a certain structure. We need to put all
our smart contracts into the contracts folder. Migration scripts should go to the migration directory. All of our
tests should go into the test folder, and these tests can be either in Solidity or in JavaScript, and we can
configure our project using a JavaScript configuration file, which we can call either truffle.js or truffle‑config.js.
Truffle deploys smart contracts using migration steps implemented in JavaScript, and each migration step is
executed only once. And to make any changes, we need to add another migration script and then run truffle
migrate again. Now this concludes this module on Truffle framework, and in the next module, we will dive deeper
into Solidity and learn how to build even more sophisticated smart contracts.
Developin
Contracts g Advanced Smart
Introduction
Hi. This is Ivan Mushketyk with Pluralsight, and welcome to the next module of this course, Developing Advanced
Smart Contracts. So far in this course, we're mostly using core Solidity features. But in this module, we will dive
deeper into Solidity and learn more advanced features of this language. And to put our skills into practice, we
will first implement a smart contract that will be using all of these new advanced features we are going to learn.
And in one of the later modules, we will see how we can build a web front end, a web application that will be
using our smart contract. Now, here's what specifically we are going to learn. First of all, we will see how we can
work with time in smart contracts, and this is a topic that we have been avoiding so far. Then we'll see how we
can send payments using smart contracts and how we can receive payments. We will then learn how we can
reduce code duplication in our functions by defining custom function modifiers that, for example, allow us to
conveniently reduce validation logic. We will then see how we can implement smart contracts interaction, how
smart contracts can call or deploy other smart contracts. And finally, we will see how we can use events, and
events is away in Ethereum to record additional information about transaction execution.
Crowdfunding Contract
Before we dive into learning more about Solidity, let's briefly discuss what application are we going to build? And
in this module, we will build a crowdfunding application, which allows to collect funds for a specific cause, and it
will be somewhat similar to Kickstarter application. Our application will only implement the basic crowdfunding
flow. Every crowdfunding campaign will have a deadline, and by this deadline, it expects to collect a
predetermined amount of funds. If it is successful, if the money are collected before the deadline, then these
funds will be transferred to a beneficiary, and if the campaign was unsuccessful, then the collected money will
be refunded. Now, here is how it's going to work. First, we would have to deploy a smart contract for each
crowdfunding campaign, and we would have to specify three parameters, the amount of money we want to
collect, the deadline of the campaign, and an Ethereum address of the beneficiary that will receive money if the
campaign is successful. Now, other users on the network can contribute and send ether to this campaign. Now, if
the campaign is successful, then the beneficiary who control this address will be able to collect the funds. If,
however, the campaign was unsuccessful, then the users can get the refunds for their payments and the
beneficiary will get nothing.
Enums
So far we have not covered one more value type in Solidity called Enum, and similar to other languages, Enum
allows us to define a custom type that can have a fixed set of predefined values, and it is useful in many cases.
For example, we can have an Enum type that represents a day of the week and it will have seven different
possible values. Or we can represent a state of a contract in Enum, for example, in our contract we'll have
different states for different stages of crowdfunding campaign, and we'll represent it using Enums. Now, we
could also use integers for this, for example, we could have just a number representing a particular day of the
week, but using Enums is more readable and much safer. Let's see how we can use Enums. First of all, to define
an Enum, we need to use the enum keyword and provide the name of our type, and then in curly brackets we
need to list all possible values an Enum type can have. We can then define a variable of this new type and assign
one of the Enum values to it, and of course, we can also read this value later, for example, to compare it with an
expected value. As I mentioned before, we will use Enum types to represent the current state of our
crowdfunding campaign, and to do this we will use an Enum type that will have the following states. First of all,
the Ongoing state will represent that the smart contract is still accepting donations. From this state, it can either
go to the Failed state if we fail to connect the necessary amount of funds, or to the Succeeded state, otherwise.
And once the collected funds are received by a beneficiary, the contract state will change to PaidOut. To
represent the state, we will have a variable in our smart contract of an Enum type that we will define, and we will
use it to decide what operations are allowed in each state. For example, we can have an Enum type that
represents a day of the week and it will have seven different possible values.
Call Function
Just to finish on the topic of how we can send payments from a smart contract, we need to talk about another
way of doing this apart from send and transfer. The issue with using send and transfer is that these functions
impose a gas limit. A receiving smart contract can only spend up to 2,300 gas when it processes incoming
transaction. So this is just enough gas to receive a payment and quickly update a state of a smart contract in
response to a payment. But unfortunately, a receiving contract can easily run out of gas because that's quite a
small amount of gas to spend. As a workaround, we can use the call method, and a call function is a general
purpose function that allows us to call an arbitrary method on any smart contract. So it wasn't added specifically
to send payments, but the benefit of using it is that it does not impose a limit on how much gas a receiving
contract can use. And because of this, this is a recommended way of sending ether. Now, here's how we can use
the call function. If we have an address, we can call the call method on it, and if we want to send some ether, we
should use the value parameter and specify how much ether to send. With call method, we also need to provide
a method to call, and if we don't need to call any method and just want to send ether, then we need to provide
an empty string as this parameter. Call method can do more than that, we can use it to call an arbitrary method
on a smart contract. If we want to call a method called foo that receives a single uint parameter, then we need to
specify a method signature, an argument that you want to send, then wrap it using the encodeWithSignature
method, and pass the result of this method to the call function. And the call function will see that you want to
call this specific method and it will also send one ether when it calls a method. Now, at this stage, you might be
wondering, should you just use call all the time, and if it has any downsides apart from this slightly odd syntax. As
it turns out, there is a downside and it is called a reentrancy attack. We will see how it works, why it is dangerous,
and most importantly, how to defend against it. Now, one thing to keep in mind is that when we send ether to an
address, we don't know who we're sending it to. It can be user, an externally owned account, or it can also be
another smart contract. And as we know, smart contracts can execute code when they receive payments, and
they also can call the smart contract that sent ether to them, and this will all be happening as a part of a single
Ethereum transaction. Well, this doesn't sound too bad, isn't it? But it can lead to some dire consequences. As
an example, let's say that we have a smart contract, and users can contribute ether to this smart contract. It also
keeps track of how much each user has contributed, and users can withdraw ether when they want to. To
implement the withdrawal, a smart contract can have a method like this, that when it receives a transaction, it
first checks if a balance for a user is greater than 0, and if it's greater than 0, then it transfers all balance to the
sender, and to do this it uses the call method. And instead of using the whole call method syntax, let's imagine
that transferWithCall does just that. After this amount was transferred, then we can set a balance for this user to
0. Now let's see how this method can be executed. Let's say that somebody calls this method, and on the first
call our smart contract gets the balance, it's greater than 0, so everything is fine, and then it transfers the full
amount to the caller. But it turns out that the caller is not the user, but the smart contract, and when it receives
this transfer while processing an incoming payment, it sends another call to the withdraw function. Now, what
will happen in this case? We'll again get the balance, we'll check if it's not 0, and because we did not set it to 0
the last time, it is still not 0, so we will allow to send the same full amount again for the same user that should
only get one transfer. And I completely understand that it might be tricky to wrap your head around this from the
first time, it is quite a difficult topic to grasp. So, if you have trouble understanding it, I would suggest to watch
this video again and walk through this example again, or ask questions in the question section of this course. But,
how bad can the reentrancy attack be? One notable example of the reentrancy attack was the so‑called DAO
hack. A hacker used this attack to steal 60 million ether from users, and it also caused a split in Ethereum into
two different chains. Now, we won't have time in this course to go over this attack, but if you're interested in
more details, I will suggest to read The Infinite Machine that covers the history of Ethereum, and the DAO hack
specifically. But now let's go back to the practical side of things. How do we protect ourselves against the
reentrancy attack? Now, a common pattern is structure a method into three blocks. Pre‑conditions, where we
check if this method call is valid. Then, Change state, where we update the state of our smart contract. And
Interactions, where we call other smart contracts, send payments, etc. So we can structure the withdraw method
in the following way. First of all, get the balance and check if it's greater than 0, then first set the balance to 0,
and only then send the payment. So if an attacker will try to call this method again during our call, the balance
will be 0 and the attack will fail. But let's just recap what we've just discussed. To protect from reentrancy attack,
we can use send or transfer. They set a limit to the gas amount that a receiver can use, and because the amount
of gas is so small, this cannot cause a reentrancy attack. Call, on the other hand, doesn't set a limit to the amount
of gas that a receiver smart contract can use, the only limit is how much gas a user set when they sent a
transaction. But, on the other hand, using call can cause a reentrancy attack, so we need to be careful with that.
Function Modifiers
When we write smart contracts, a lot of code will be dedicated to checking pre‑conditions, such as can the
sender send these parameters? Or is a contract in a valid state? And this can cause quite a lot of code
duplication because we might need to implement the same checks in different methods. Fortunately, Solidity
has a nice feature that allows us to avoid this code duplication. And to see how it works, imagine that we have a
contract like this. Let's say we have a contract that has a special address with extra privileges that is called
owner. And let's say we have two methods, removeContract and updateContract, and we only allow the owner
to perform these operations. Now, to reduce code duplication, we can create a function modifier and then apply
these modifiers on the methods of our smart contract. And here is how they work. To define a modifier, we first of
all need to use the modifier keyword and then provide the name of our modifier, and in the curly brackets, we
need to specify the code that should be executed when this modifier is called. A modifier can contain an
arbitrary code, but our modifier will check if a caller of the method is the owner of the smart contract instance.
And then in modifiers, we can use the special underscore syntax, which means execute the rest of the function
on which this modifier is applied. To apply modifier, we need to put the name of the modifier between the list of
parameters and the body of this function. And the way it works, if this function is called, first of all, it will call the
modifier function, execute the require statement, and then it will execute the body of the function. We can also
have modifiers that accept parameters. For example, let's say we now have different roles in our smart contract
and we want to restrict different method calls to different roles. To do this, we can define a modifier that accepts
a parameter, and in the body of this modifier, we can check that the caller of the method is equal to the value of
the parameter that was sent to this modifier. And when we apply a modifier, we can pass the value for this
parameter as if we called a function. There is no restriction that we can only use one modifier on the function, we
can actually apply multiple modifiers. So for example, in this case we have two modifiers, one checks who is the
sender of a transaction, and another one checks the state of our smart contract. And, if we have a function, we
can apply both of these on the same function, in which case, first, it will execute the isOwner modifier, and then
the gameStarted modifier.
Sending Payouts
In this demo, we will wrap up the implementation of our smart contract. We will add two new methods, one to
receive refunds, and another one to send funds to a beneficiary. And as always, we'll add unit tests to check that
our smart contract works just as we expect. So first, let's add a new method that will allow us to send the total
collected amount to the beneficiary, and this is how we will implement it. First of all, we need to check if the state
of the campaign is succeeded. So we only send funds to the beneficiary if we've managed to collect the full
amount before the deadline. And then we'll use the send method to send the fullCollected amount to the
beneficiary address, and if we've managed to do this, then the state of our campaign will be set to PaidOut. But if
we fail to pay for whatever reason, then we will set the state of the smart contract to Failed to allow the
contributors to get refunds. And finally, we will implement the withdraw method that will allow to collect refunds
in case if the campaign has failed. It will only be possible to call it in the Failed state of the campaign, and in this
method, we'll first of all check that the caller has contributed some funds to the campaign, and if they did
contribute anything, then it would get the amount that they sent, and we'd use a transfer method to send the
amount of contributed back to them. Now let's add two more tests to check this behavior. We'll start with the
test that checks if the beneficiary can collect money from the campaign, and to do this, first of all, we contribute
the target amount, we increase time past the deadline, and then we finish the crowdfunding campaign. And
after this, we call the collect method to collect the received funds, but we want to check if these funds are
actually sent to the beneficiary so we get the balance for the beneficiary address before and after the call, and
then we'll check that the difference in the balance is equal to ONE_ETH, which is the amount that we've
contributed during this test campaign. And we also check that the state of the smart contract after this method
call is set to PAID_OUT. Now for the last test of this demo, we will check that the refund functionality works as
well. So here we send the amount that is less than the target amount, then finish the campaign, and then we first
of all check that the campaign has failed, and then we try to withdraw the required amount using the withdraw
method and check that the state of our smart contract was updated, and we no longer have a positive balance
for this account.
Contracts Interactions
So far, in this course, all contracts we have implemented were working in isolation, but to get the real power of
smart contracts, we need to learn how they can interact with each other, and smart contracts can deploy other
smart contracts, they can send payments, or they can call methods on other smart contracts. And learning how
we can implement interactions between smart contracts opens a lot of possibilities for developing even more
sophisticated applications. Now let's see how we can call a method on another smart contract, and let's say we
have a smart contract that implements an online shop, and we also have another smart contract that
implements a single item in this shop, to call a method on another smart contract. But first of all, we need to
know its address, and then we need to convert this address into the instance of our smart contract. And to do
this, we need to use the name of the smart contract we want to call, and in parentheses provide the address of
the smart contract. And once we have this variable, we can then start calling methods on this smart contract.
Smart contracts can also deploy new smart contracts. And again, let's say we have a similar example when we
have an online shop and it wants to deploy an instance of the shop item smart contract. And to do this, we need
to use the new keyword, specify the name of the smart contract we want to deploy, and provide the parameters
for the smart contract's constructor. And then this will again return an object that we can use to interact with our
smart contract, and as before, we can start calling methods on it. An issue was calling a method on a smart
contract or deploying a new instance of a smart contract is that both can fail and throw an error. And to handle
just that, Solidity has a try/catch statement that allows us to handle errors when interacting with other smart
contracts. This is a relatively recent feature that was only added in version 0.6 of the language. And before that,
if there was an error during a transaction execution, the whole transaction would fail. But now, we can handle
these errors using the try/catch statements, and they're somewhat similar to try/catch statements in other
programming languages, with some differences. This construct has some limitations in Solidity. First of all, it can
only work on external calls or contract creations. And as of now, it supports a limited set of error types, to handle
an error when we are calling a method on another smart contract, but first of all, we need to use the try keyword,
then to specify the method call, and then using the return statement, specify the return type of this expression.
Then, with this, we need to provide two blocks. The try block will be executed if there was no error calling this
method, and then we need to provide the catch block, and the catch block will be executed if there was an error
when calling this method. We can do a similar thing when we are deploying a smart contract, but instead of
calling a method before the try block, we need to deploy an instance of a smart contract, and then using the
returns keyword, we need to specify the return type, which is the type of this smart contract. And as before, if
there were no errors creating this smart contract, the try block will be executed, and if there were any errors,
then Solidity will execute the catch block. Using the catch block, we can catch different error types using
try/catch, but so far, we have only a limited set of errors; we can catch. If we write a catch block like this where
we write an error, which has a single string field, then this block will be able to catch errors created by revert or
the require statement. If we catch a panic error, this will be able to process errors from the assert function. To
handle any other error type, we need to catch a byte array like this. And if we don't provide the type of an error, if
we just write the catch block, no error type, and the block to execute, this will catch any error that was thrown.
And we don't need to write a single catch block, we can combine catch statements. So, for example, in this case,
we have two catch statements, and the first one will be executed if an error was caused by revert or require
functions, and if the error was caused by anything else, then Solidity will execute the catch block that will catch
any other error.
Events
Now let's talk about another Ethereum concept called events. And events is a mechanism that allows us to
notify about an event that happened during a transaction execution. Web3 clients can subscribe to these
events and monitor when particular events happen to a smart contract. Those events are also stored
permanently in the blockchain history so we can read them later. The major downside, however, is that contracts
themselves cannot subscribe to events, only web3 clients can do this. Events also allow us to store additional
information to the blockchain history, and the reason why we might do this instead of using a regular state field,
storing an event is cheaper than storing it in a contract state. Now let's see how we can use an event in Solidity.
First of all, we would need to define an event type, and to do this, we need to use the event keyword, specify the
name of the event, and then list parameters of an event. To generate an event, in the smart contract, we need to
use to emit keyword, provide the name of an event that we want to emit, and provide arguments for the
parameters of an event. Now in web3.js, we can subscribe to events, and to subscribe to a particular event, we
need to get the events object, dot, the name of our event type, and then we want to specify additional
parameters, what events of this type do we want to read, and we can specify fromBlock, which is the number of
a block from which we start reading the list of generated events, and toBlock, which is the number of the block
to which we want to read the list of events. And these two are optional, so we don't need to provide them if we
don't need to. But we also need to provide a callback function, and this callback function will be called on every
event that web3.js reads. Now, sometimes we might want to filter events not just by block numbers, but also by
the values of their attributes, and to do this, we need to use indexed attributes. These indexed attributes allow
us to filter events by particular attributes' value, and all we need to do in our events definition is to use the
indexed keyword. Here's how it works. If we want to define an event with an indexed attribute, we would just use
the indexed keyword when we define an attribute, and then we can emit an event in exactly the same way as
before. And then to filter events by a particular attribute value, we'll first of all subscribe to them as before, but
then we can provide the filter parameter where we'll specify what attributes do we want to filter by, and in this
case, we specify that we are only interested in events where the value of the type attribute is either 0 or 1. And
then to get this list of events, we need to define the callback function that will be again called on every event.
Using Events
In the last demo of this module, we'll define an event type, and we'll emit this event from our smart contract. And
as always, we will write a unit test that checks that this event was correctly emitted. First, let's start by defining a
new event type. And in this contract, we will define a new event type that will signify that a crowdfunding
campaign has finished. Our event will be called CampaignFinished, and it will have three attributes, the address
of the campaign, the total collected amount, and the Boolean succeeded flag that will specify if the campaign
finished successfully or if it has failed. And to emit this event, we will go to the finishedCrowdfunding method.
And in this method, we will emit this new event. And to do this, we will convert this reference to the contract
address, get the total collected amount using the totalCollected method, and to decide if the smart contract
was successful, we'll just use the collected flag, which will be true if the campaign managed to collect the
necessary amount of funds and false otherwise. And now let's implement our final test for the smart contract.
And this test will verify that our smart contract correctly emits this event. We'll first of all move time past the
deadline, we'll finish the campaign, which should fail at this stage. But notice that when we execute the
finishCrowdfunding method, we'll get the receipt object from this. And receipt object in Ethereum basically
contains the transaction's outcome, and we can get it either for a transaction that we execute or we can get it
for any of the historical transactions. On this receipt object, we can get the logs field. And the logs field contains
the list of events that were emitted during the transaction execution. And we want to verify that this transaction
has generated one event. And then we can get this event, and first of all, we want to check that the name of this
event is campaignFinished, which is the name of the event that we have emitted. And then we can check that
the arguments of this event were correct. We check that the address of the smart contract is set correctly, that
the total collected amount is equal to 0, and that it has failed, meaning that the succeeded field is equal to false.
Okay, and now having this, we can perform the last test's execution in this module and check if our smart
contract works correctly. Okay, so all of our tests are passing, which means that we have a smart contract that
can accept contributions, that can perform payouts, refunds, and it also can generate events, and it can
correctly work with time, meaning that its behavior depends on the current time.
Summary
In this module, we've covered quite a lot of important topics. First of all, we've covered how to work with time in
smart contracts. We saw how to use time units in Solidity and how to test smart contracts that rely on current
time. We have dedicated a big portion of this module to working with payments, and we've learned how to
accept payments and how to send payments, and we've covered three different methods for sending payments.
And we also covered reentrancy attacks and how to defend from them. We also saw how we can implement
custom method modifiers and how to use them to reduce code duplication. We then learned how to implement
smart contract interactions, call other smart contracts, create smart contracts, and how to handle errors in
Solidity. We finished this module by covering events in smart contracts and how we can record additional
information about transaction execution using events.
Reusi ng
ContractsCode in Smart
Introduction
Hi, this is Ivan Mushketyk with Pluralsight, and welcome to the next module of this course, Reusing Code in
Smart Contracts. And in this module, we'll cover two ways of reusing code in our smart contracts. One is
inheritance in smart contracts, and using inheritance, we can create a new smart contract that derives from an
existing smart contract and can reuse some of its code. We will also cover a related topic of polymorphism, a
mechanism when an actual method being called in a smart contract is decided in the runtime when a smart
contract is executed. And we will also talk about how we can use libraries in Solidity. We'll see how we can define
our own libraries, and we will learn how we can use libraries created by other developers, and we will learn how
we can import these libraries into our project using Truffle.
Contracts Inheritance
Let's start this module with discussing inheritance in smart contracts. And you might be wondering, what is
inheritance? The core idea is that it allows us to define a new smart contract by reusing code of an existing
smart contract. And if you're familiar with object‑oriented programming languages, then this concept should be
familiar to you. Just as we can inherit classes in these languages, we can inherit new contracts from existing
contracts. But the way inheritance is implemented in Solidity is most similar to Python. When we inherit smart
contract from an existing smart contract, we will reuse the code of an existing smart contract, but we can also
add new methods to the new smart contract, we can replace methods, etc. Now let's look at a simple example of
smart contract inheritance. Let's say that we have a smart contract called Restricted, and it has two methods,
allowed and changeState. And when changeState is called, it will first use the allowed method to check if the
caller of this method can perform this operation. And by default, Restricted will allow all callers to perform this
operation. But we can also define a new smart contract called AllowsOwner, and using the is keyword to specify
that it inherits the Restricted smart contract. Now, if we leave it at that, if we don't define anything in the body of
the smart contract, it will just have the same methods and the same state as the Restricted smart contract. But
we can also replace implementations of some of the methods that we get from this smart contract. For example,
we can replace the implementation of the allowed method and change it so only the owner of the smart
contract can perform this operation. So if we deploy AllowsOwners smart contract, it will still have two methods,
the changeState from the restricted smart contract, but allowed will be replaced with its own implementation.
One important bit of terminology that we'll use for the rest of the module is that a contract that inherits another
contract is called a child, and a contract being inherited is called a parent. We can also inherit smart contracts
with constructors. So let's say we have a parent smart contract called Auction, and it has a constructor with one
parameter, the number of rounds in an Auction. If we inherit this contract, we need to provide the values for its
constructor. And to do this, when we define a constructor for a child contract, we need to write the name of the
parent contract and provide the value for its only parameter. So far, we're only discussing how to inherit a fully
defined smart contract, but there's also another interesting concept called an abstract contract. And abstract
contracts are contracts that cannot be deployed and can only be inherited by other smart contracts that we
can deploy. Abstract contracts usually define just the part of the functionality, and they serve like a template for
implementation, leaving some methods unimplemented. When we inherit an abstract contract, child contracts
can define and specify the remaining implementation. Here is an example of an abstract contract where we have
a method called pay, but it has no definition. So, we cannot deploy a contract like this, but we can inherit the
smart contract, we can create a new smart contract that implements all unimplemented methods from its
parent, and then this smart contract we can deploy. Now to wrap up the topic of inheritance, let's talk about the
concept of access modifiers and how they are related to contract inheritance. If you recall, in one of the earlier
modules, we were discussing two similar modifiers, internal and private, and they're very similar. If we use these
modifiers, a method can only be called from the inside of the smart contract. It cannot be called from the
outside. If we have a method with the internal modifier, it can be called by the contract itself or by any of its child
contracts. On the other hand, method with the private modifier can only be called by the contract where it is
defined.
Polymorphism
When talking about inheritance, it is almost impossible to avoid the topic of polymorphism. And in a nutshell,
polymorphism is a mechanism that affects how we can interact with smart contracts that use inheritance. And if
you've never encountered polymorphism, it is easier to explain how it works using an example. Before we dive
into any theory, let's say we have a smart contract called ShopItem, and it has a single method. And this method
does not have an implementation, so we can't deploy this smart contract. And also let's say that we have
multiple child smart contracts that inherit ShopItem, and they implement this startSale method. For example,
here we define the logic for starting an auction when somebody wants to sell an item. Now where it becomes
interesting is if we have a reference to a parent smart contract, the ShopItem. And you might say, wait, we know
that the ShopItem is an abstract smart contract, we cannot deploy it, and that would correct. But we can define
a reference to an abstract smart contract, and we can then assign a reference to the child smart contract in the
place where we expect the parent smart contract. And then we can use the ShopItem reference and call the
method using this reference, but it will call the method of the child smart contract. So just to reiterate this again,
we have a reference to the parent smart contract, but when we call the method, it will call the method of the
smart contract it points to. So when we write this code, we actually don't know what method will be called at this
point. The exact method is defined during the execution of the smart contract. So now that we saw a particular
example, let's describe a general rule. First, if we have anywhere in our code a reference to a parent smart
contract, we can pass there a reference to a child smart contract. And this reference can be a state variable, a
function parameter, etc. And once we have a reference of the parent contract type, if we call a method on it, it
will call a method of a contract it refers to, and this will be despite the type of the reference. It will always call the
method on the smart contract the reference is pointing to. But you might be wondering why is this useful, how
we can realistically apply this. And the concept of polymorphism allows us to implement the code that interacts
with some smart contract. But then the same code can work with different implementations, even if a particular
implementation doesn't exist yet. And we can use polymorphism with regular contracts or we can use abstract
contracts. And we can even use polymorphism with contracts that have no implemented methods and only
have methods definitions. And these smart contracts that only have method definitions are called interfaces.
They can define methods that other smart contracts need to implement, and then we can write code against
them. And this code will work with any implementation. And the concept of interfaces in Solidity is very similar to
similar concepts in other languages, like Java, TypeScript, C#, etc.
Using OpenZeppelin
In this demo, we will add OpenZeppelin library to our project, and then we enhance the functionality of our
smart contract using this library. As we discussed before, to use third‑party code in a Truffle project, we first of all
need to run the npm init command. And this command will create an NPM package that will then allow us to
download search party packages with Solidity code. Npm init command wrote this configuration to the package
JSON file and having this, we can now add third‑party code to our project. And to do this, we would use the npm
install command. And that's it. Now we have the OpenZeppelin smart contract library in our project, and we can
use it to improve our smart contract. Now to find out what OpenZeppelin can do for us, we can go to the official
OpenZeppelin website, go to Contracts, then go to the docs. And this page has a list of all the smart contracts
that are available in OpenZeppelin. And if you go to the left to the Access page, it will contain the
documentation for the Ownable smart contract that we will use in this demo. And if you're interested, you can go
through these methods and see what is available in this smart contract. Now it also includes a code snippet that
we can use to import the smart contract into our code. Now in our project, we first of all will paste this code, so
now we have access to the Ownable smart contract. And then we can inherit our smart contract from Ownable.
So at this stage, our smart contract will have methods and State from Ownable, and we can start using them in
our smart contract. What we'll do, we'll implement a new method that will allow us to cancel the crowdfunding
before the deadline, and it will only be available in the Ongoing state and beforeDeadline, but we also specified
that only the owner of the smart contract can do this, and we do this using the onlyOwner modifier defined in
the Ownable smart contract. If the owner calls that cancelCrowdfunding method, we change the state of the
smart contract to Failed, which will stop the campaign and will allow users to get refunds. Now, the only
remaining thing that we need to do is we need to transfer the ownership of the smart contract when we deploy
it. And to do this, we will use the transferOwnership function that will transfer the ownership of this smart
contract to the beneficiary address. Okay, so now when we have updated our smart contract, we can add two
simple tests. In the first test, we will check that the beneficiary can cancel the crowdfunding. So we'll call the
cancelCrowdfunding method, and then we'll check that the state of the campaign is now FAILED. In the second
test, we'll check that only the owner can cancel a crowdfunding campaign. We will try to call the
cancelCrowdfunding method, but we will do it from a different account. And in this case, we expect that it will
fail and we'll get an error from the OpenZeppelin library, caller is not the owner. Okay, now, let's see if our smart
contract works as we expect. And as you can see, all of our tests are passing. So we've added third‑party code to
our project, we've inherited from one of its contracts, and we've used that functionality to enhance our smart
contract.
Libraries In Solidity
So far in this course, we're putting all logic of our applications into smart contracts. These contracts were either
developed by us or by a third party like OpenZeppelin. But with Solidity, there is another way to reuse and
deploy our code, which is libraries. Libraries in Solidity, they work similarly to a contract that doesn't have any
state and just works as a collection of functions that we can call. Now, here's an example of a simple library. To
define a library, we need to use the library keyword, then the name of the library, and then in the body of this
library, we can define our functions. And in this case, this will be a function that just returns the maximum of two
numbers, a and b. Having this trivial library, we can then use it in one of our smart contracts. To do this, we need
to use the import statement and specify that we want to import our library, and then to call our method, we need
to specify the name of the library, then dot, and then the name of the method we want to call. Solidity has also
another way of using library functions. It allows us to add a library method to an existing type. So, for example,
let's say we have this library, and it has a single method that receives the only parameter of integer type, and this
function specifically will just return the opposite number to what was passed to it. Having such library definition,
we can add this method, or in Solidity terminology, attach it to the integer type. And to do this, we need to use
the using keyword, specify the library that you want to add, and specify on what type they will want to add
functions from this library. And once we specify that we want to have functions from this library on the integer
type, we can just call the methods of this library on integers as if they were defined in Solidity itself. But notice
that since we only added this library for integer type, we cannot call these methods on any other type apart
from integers. So, for example, we cannot use it for Boolean type.
Linked Libraries
We've already covered how we can create libraries, but it turns out that there are two ways to deploy a new
library with Solidity. One way is to embed the code of the library in the smart contract that uses it. So, in this
case, if we have multiple contracts that use the same library, then the library code will be embedded in each one
of these deployed smart contracts. The other way is when a library is deployed separately. So, in this case, we
deploy a library just as we deploy a smart contract, and if we have multiple smart contracts that depend on it,
then they will use the same deployed instance. And what might be quite confusing is that what defines how
library is deployed is what access modifiers we have on library methods. So let's look at an example. If we have a
library and all of its methods have the internal modifier, then the code of this library will be embedded in other
smart contracts that use it. However, if we have a function with the public modifier, then this library will be
deployed separately. And in libraries, functions can be either internal or public and what modifiers we use
defines how this library is being deployed. Now, let's talk about the concept of deployable libraries. A deployable
library is just another type of smart contract. It has no storage, meaning that it doesn't have any state variables,
like we had in smart contracts. A deployed library, just as any other smart contract, will be available for anyone to
call. And the main benefit of deploying a library is that it allows us to make contracts that use it smaller, which
allows us to save gas when deploying smart contracts. When we deploy a library, it only makes sense to have a
single instance. This is because, first of all, a library doesn't have a state, and they cannot be changed, so there is
no need to have multiple instances. If we deploy a library to use this library, Truffle has a special mechanism
called linking that allows us to connect a deployed library with a smart contract, and essentially, what this linking
means is that Truffle will write the address of the library into the smart contract's code. And here is how this
process looks like. First of all, we need to deploy a library with Truffle. And once we do this, we will have an
address of this library. Then we need to define a smart contract that uses this library. And with this compiled, we
won't be able to deploy it yet because, at this stage, it still won't have the address of the library that it will be
using. To connect this contract to a library, we need to link them together, which means that Truffle will write the
address of this library into this unlinked smart contract. And as a result of this process, we will have a deployable
smart contract. And when it is deployed, it will be using this library. To link a library with Truffle, we need to define
a new migration step. We need to import the library that we want to use and the smart contract that depends on
it. Then in the migration‑step definition, we need to deploy the library just as we deploy any other smart
contract, and then we need to call the link function and specify that we want to link this library with this smart
contract. We can also link an existing library with a smart contract in Truffle. And to do this, we need to go
through the same steps. But in the migration step, we need to use the at function that specifies that we don't
want to deploy this library, we want to use an existing library at this address, and then we can link our library to a
smart contract just as before.
Implementing Libraries
In this demo, we will write a simple library in Solidity, and it will be almost trivial. We'll just implement two
conversion methods for time and ether values, but it will allow us to see how we can use a library in our smart
contract and how we can configure it in Truffle. To implement a library in our project, we first need to create a
new file for it, and I'll call it Utils. And first we need to add the definition of our library, which includes the same
pragma statement as before, the same license‑identifier as before, but instead of using the contract keyword, we
will use the library keyword. And in this library, we will add two methods. The first method etherToWei, will allow
us to convert an amount of ether into wei, and to do this, we'll multiply sumInEther by 1 ether value. And the
second method will be very similar. It will allow us to convert minutes into seconds, and to do this, we'll multiply
timeInMin by 1 minute. Now to use this library, we need to go back to our smart contract. And first, we need to
import our library. And second, because both methods in this library received a single parameter of uint, we can
attach these methods to the uint type. And to do this, we are using the using keyword and specify that we want
to add all methods from the Utils library on the uint type. Now the only thing that's remaining is now to use those
methods in our smart contract. And I'll use them in the constructor where we convert ether values and minutes
into weis and seconds. Okay, this is it. We have updated our smart contract, and because we did not change any
functionality, our tests should just pass as they are. So let's try to run our tests. But unexpectedly, our tests are
failing, and what it says is that Crowdfunding contract contains unresolved libraries. And what we need to do, we
need to deploy and link these libraries before we can use them. And it also specifies the name of the library that
is unresolved, which is our library. And just as we discussed before, we need to add a new deployment step that
will link our library to our smart contract. And I'll add a new migration step, deploy_utils. And here is the definition
of the deployment step that we need to use. First of all, we input the Utils library and the Crowdfunding contract
into the step, and then in the definition of the step itself, we first deploy the Utils library, and then we'll link it to
our Crowdfunding contract. And now this migration step will also be used by our tests. And if we run our tests
again, and as you can see, now all of our tests are passing So in this demo, we have added a new library to our
project. And this library has to be deployed and linked to our smart contract, which we've done in a new
deployment step.
Summary
In this module, we've looked into how we can reduce code in smart contracts. First of all, we've learned how we
can use smart contracts inheritance to define a new smart contract with similar behavior. We've also learned
about polymorphism in Solidity that allows to dynamically select a method to call during the contract execution.
We then saw how we can use third‑party code in Truffle projects. And we've added OpenZeppelin library in our
project. We inherited our smart contracts from one of the contracts in this library and used it to enhance
functionality of our smart contract. We've also learned how to define our own libraries in Solidity, and we've
learned that there are two types of libraries, embedded libraries, when a code of the library is included in the
smart contract that uses it, and deployed libraries that are deployed as other smart contracts, with the only
difference that these smart contracts don't have any state and only have methods that we can call. All right, so
this module finishes the implementation of our smart contract. But in the next module, we will see how we can
build a web application that is built on top of it.
React Overview
Since we're going to use React to implement our web front end, it's important for you to know at least the basics
of React. And that's why I added this clip, so you will be able to later follow along as we develop our web
application. And this won't be a course about web development in React, and we'll just cover the bare minimum.
If you want to know more about React, there are a lot great courses on Pluralsight you can watch after this one.
If you are a React expert, you can skip this clip since we're not going to cover anything related to Ethereum or
Web3 here. But specifically, we will cover functional components in React, and we will cover React hooks. And if
you're familiar with both of these, you can skip this clip. Well, the basic idea of React is that we always surrender
or generate our UI from the application state. So our application has a particular state, and React renders the
current UI from the state using the code that we define. If we need to update this UI, for example, if the user's
done something with it, we don't update the UI directly. Instead, we always change the state of our application
and React will notice this and rerender our application accordingly. So, all we need to do is React to find how to
render our UI from the application state and then update the state when necessary, and React will do the rest.
To define how to render UI in React, we need to break down our application into components. And currently, the
most popular way of defining components is called functional components. To define a functional component,
all we need to do is to define a function. And you see it's name is intentionally starting with a capital letter since
this is going to be the name of our new component. This component receives data that it should render, which is
called properties, or props for short. And in the implementation of this component, we can use regular HTML
tags to define how our component should be rendered. We can all surrender nonstatic content such as values
of the properties of our component. And to do this, we can inject bits of shell script code. But to inject them, we
need to put them in curly braces like here where we render the value of the counter property. We can also use
components in other components. For example, if we want to define another component that relies on the
Counter component, we need to use this HTML‑like syntax where instead of the name of an HTML tag, we use
the name of our component. And to configure our component, we need to provide values for its properties
using, again, HTML syntax. So if we add the property Counter here, it will appear in the props object when our
component is rendered. Unless we're building a very simple application, we would have to break it down in the
multiple components. For example, in our case, we might have a page to display information about a particular
campaign. We can implement it using two other components. One would have the sole purpose of displaying
information about the campaign and the other one would display controls to interact with it, for example, to
allow to contribute to campaign if it isn't going. These components can in turn be built from other components,
such as tables, text inputs, buttons, etc. We can also use third‑party components. And in this module, we will use
Semantic UI library that implements a number of third‑party components. To use a component from another
developer, we need to first import it, like in this case, when we import an Input component from Semantic UI
React library, and then we can use it when we define a our own component. We can also configure how it should
be displayed, and here we provide properties for how this component should be rendered. And each
component in Semantic library has a ton of properties that allows us to configure them. So far, we're looking at
components that receive data to display from other components. But each component can have its own state as
well if we want to have it. In modern React, to implement this, we use a mechanism called hooks. And we won't
delve too deeply into this, but the reason why they're called hooks is because they sort of allow to hook into the
React's lifecycle, the lifecycle of components in the React library. And to manage state per component, we need
to use that useState function. And let's briefly see how it works. So here's an example of a component that has
its own local state, and we define it using the useState function that allows us to define a single state field. To call
this function, we need to pass the initial value of this field, and this call will return two variables. The first one is
the current value of the state field, and the second one is a function that allows us to set the new value for this
field. Having this, we can do two things. First of all, we can display the current value of this field, and to do this,
we need to use an expression in curly brackets. And second, we can update the state field using the function
that useState has returned. And as soon as we update the state, React will notice that the state was updated
and it will rerender this component and display new data.
Check MetaMask
In this demo, we will add a check to our application if a wallet plugin is installed in the user's browser, and then
we will also display an error if a plugin like MetaMask is not installed. To implement this feature, I will add a new
component that will only display a Campaign page if a plugin is installed, or it will display an error if a plugin is not
installed. So add a new file here, I'll call it CampaignPage, and here is the implementation of this component. So,
here we define two functions, the function that implements our component and a helper function. And let's start
with the helper function. So the helper function is called isWalletPluginInstalled, and, as the name suggests,
checks if we have a plugin like MetaMask installed, and if it does, it returns true if the ethereum object is defined
or false otherwise. And to do this it uses this weird JavaScript trick when it uses this double exclamation mark.
And what it does, it converts any expression into true or false. If ethereum is defined, it will convert it into true, if
ethereum is undefined, it will convert it into false. And then we use this function in the definition of our
component. If the plugin is not installed, then it will display a text saying wallet is not available and ask a user to
install a wallet. If the wallet is installed, we just display the Campaign page, and we have already gone through
how the Campaign page is implemented. Now, the last remaining thing is that we need to use this component in
our router. To do this, we go to the App file. We don't need to use the Campaign here anymore, so instead we will
use the CampaignPage. And instead of displaying the Campaign component we will display the CampaignPage
component over here. So if a user goes to this address, we'll display in the CampaignPage, which will either
display the campaign itself or the error message. Let's test our code. And to test this code we can either remove
our plugin, but it will also remove the account that we have created, so a better way is just to disable the plugin
and refresh the page. To do this, we need to go to the Manage Extensions page and click on this button to
disable MetaMask. So now it will look as if MetaMask is not installed, but will still keep all the data in our plugin.
Let's refresh the page. And as you can see, we got our message, we got this Wallet not available error. And to
see if it really works, we can just re‑enable the extension. And if we refresh the page, the error disappears. So it
behaves just as we want. And using the code like this, we can instruct the user to install the MetaMask plugin or
other wallet plugin if they don't have it in their browser.
Connect to MetaMask
For our application to interact with Ethereum, we first need to connect it with MetaMask. And essentially, what
this process of connecting to MetaMask means is that a user needs to specify what accounts can be used with
what centralized applications. And by default, MetaMask does not allow any accounts to be used with any
applications. These permissions are given on the per account basis. To connect to MetaMask, we need to call
the requestAccounts method on the user object. And when we call it, MetaMask will display a model window
where the user can select which accounts can be used with the current application. A user can check these
check boxes for accounts that they allow to be used and then click Next or they can click Cancel if they don't
want to to connect any of the accounts to our application. And this requestAccounts method is only available
with a provider from MetaMask or a similar browser plugin. And if our application is already connected to
MetaMask, then this method won't display a model window, and instead, it will return an array of accounts that
are already connected with MetaMask. Just to wrap up this topic, MetaMask has two different ways of getting
the current account. We've already covered the requestAccounts method that can either return a list of
accounts or open the Connect With MetaMask window. But MetaMask also has another method called
getAccounts, which either returns current MetaMask accounts or it returns an empty array if no accounts are
connected. And this last method is useful if we want to get a list of accounts from MetaMask if our application is
already connected and at the same time don't trigger the connect flow if we're not connected yet.
Sending a Transaction
Now for the last demo of this module and this course, we will see how we can send a transaction from our web
front end to our smart contract. And specifically, what we are going to do, we will try to implement the logic for
contributing to our crowdfunding campaign. I've did some minor changes to our smart contract. In the
ContributeInput component, I have added two more properties, the contractAddress that we will interact with
and the current Ethereum account selected in MetaMask, and I've also passed these parameters from the
Campaign component to the campaignInteractionSection and then to the component itself. Okay, now to send
a transaction to our smart contract, we need to do a few things. So, first of all, we need to import the getWeb3
function. Then here we need to create an instance of Web3. And then, we already have a function called
onContribute. And to just remind you, this is a function that is called when a user clicks on the Contribute
button. At the moment, it just displays a message, but we can replace this with sending transaction to our smart
contract. And here is a full implementation in this function. The first thing that we do, we get the
contributionAmount that is specified in Ether because a user just inputs the amount in Ether and converts it to
Wei using the toWei function. Second, we estimate the amount of gas that we need to perform this transaction,
and we do this using the estimateGas function on web3. And finally, once we have the gasEstimate and the
amount to send in Wei, we call the sendTransaction method on the Web3. And notice that we don't need to use
the getContract function to get the Contract object to send a transaction to it as we did in the previous demo,
where we were trying to read data from the smart contract. This is because we're not calling any methods on the
smart contract, we're just sending a payment from the CurrentAccount to the contractAddress. We also specify
the amount we're going to send and the gasEstimate. In the final bit of this function, we subscribe to different
events that we can get from Web3. We'll get an event when we get the transaction hash, when we get a receipt,
and when we get a confirmation. Having this, we can go back to our application, specify the amount of Ether that
we want to contribute, click Contribute, and you will get a MetaMask notification that asks us to confirm this
transaction, and I'll click Confirm. I'll refresh the page, and as we can see, the Contributed amount changed from
0 to half an Ether, which is this amount in a Wei. And this wraps development of our decentralized application
that now has a smart contract backing it and the web front end that we've just developed.
Summary
Congratulations, you finally reached the end of the course. And in this course, we went through quite a journey.
And in this last clip, I would like to sum up what we've learned in this course and in this module. So, we spent
most of the time learning about how to build decentralized applications, and decentralized applications have
quite a different architecture from regular applications. In the case of Ethereum, these applications run on top of
the Ethereum network. And Ethereum is a platform provided by a network of peers that provide infrastructure
for the network, but they cannot control the network itself. The underlying data structure that allows this is
called the blockchain. And a Blockchain is just a chain of blocks, every block contains a list of transactions that
were executed in this block, and it contains a reference to a previous block. This chain of references goes on
and on until it reaches the first block, that is called the genesis block. Ethereum works as an
application‑development platform based on top of the blockchain, and it allows to build various applications on
top of it. To execute code in Ethereum, we need to define and deploy smart contracts to the network. To interact
with the Ethereum network, we need to use the Web3 API. That allows us to read data from the network, deploy
smart contracts, send transactions, etc. This API is provided by Ethereum clients, and together, the network of
interconnected clients is what creates Ethereum network. Executable logic in Ethereum is provided in the form
of smart contracts. We write these smart contracts in different languages, such as Solidity and Vyper. But to
deploy a smart contract to the network, we first need to compile a smart contract into a bytecode using a
compiler for corresponding language, and then we can deploy a smart contract to any Ethereum network. In this
module, we've implemented a single‑page web application that interacts with a smart contract. It downloads
CSS and JavaScript resources from a web server and then interacts with the smart contract by sending
transactions to it and by reading data from it. To interact with an Ethereum network, we had to install the
MetaMask plugin. And this plugin provides a Provider object that is connected to an Ethereum network. This
provider was then utilized by the web3.js library used by our React components that we've implemented to build
the interface of our application. And that's it. Thank you for watching the course. However, I would like to ask you
for a small favor. If you've enjoyed the course, I would really appreciate if you could give it a good rating on
Pluralsight, share it with your friends, share it on social media, and let other people know about it. I also have
other Pluralsight courses that you might be interested in, so I encourage you to look at the Pluralsight library and
see if you can find something interesting there. You can also be notified when I release a new course. So if you
click on the Follow button on my Pluralsight Profile page, you won't miss any more of the good stuff. I hope that
you've enjoyed this course, and see you next time.
Course author
Ivan Mushketyk
Ivan is a Principal Software Engineer and a Tech Lead who is passionate about Big Data and Cloud
Computing. He has worked for numerous big IT companies including Samsung and Amazon Web Services.
He...
Course info
Level Intermediate
Rating (29)
My rating
Duration 5h 48m
Released 3 Nov 2022
Reviewed 31 Dec 2022
Share course