This chapter is an introduction to the development tools, languages, and frameworks used for Ethereum smart contract development. We will examine different methods of developing smart contracts for the Ethereum blockchain. We will discuss various constructs of the Solidity language in detail, which is currently the most popular development language for smart contract development on Ethereum.
In this chapter, we will cover the following topics:
There are a number of tools available for Ethereum development. The following diagram shows the taxonomy of various development tools, clients, IDEs, and development frameworks for Ethereum:
Figure 14.1: Taxonomy of Ethereum development ecosystem components
The preceding taxonomy does not include all frameworks and tools that are out there for development on Ethereum. It shows the most commonly used tools and frameworks, including some that we will use in our examples throughout this chapter.
There are a number of resources available related to development tools for Ethereum at the following address: http://ethdocs.org/en/latest/contracts-and-transactions/developer-tools.html#developer-tools
Since we have discussed some of the main tools available in Ethereum in previous chapters, such as the Remix IDE and MetaMask, this chapter will focus mainly on Geth, Solidity, Ganache, solc
, and Truffle. Some other elements, such as prerequisites (Node.js), will also be discussed briefly.
We'll start by exploring some of the programming languages that can be used in the Ethereum blockchain.
Smart contracts can be programmed in a variety of languages for the Ethereum blockchain. There are six main languages that can be used to write contracts:
As Solidity code needs to be compiled into bytecode, we need a compiler to do so. In the next section, we will introduce the Solidity compiler.
Compilers are used to convert high-level contract source code into the format that the Ethereum execution environment understands. The Solidity compiler, solc
, is the most common one in use, which is discussed next.
solc
converts from a high-level Solidity language into Ethereum Virtual Machine (EVM) bytecode so that it can be executed on the blockchain by the EVM.
solc
can be installed on a Linux Ubuntu operating system using the following commands:
$ sudo apt-get install solc
If Personal Package Archives (PPAs) are not already installed, those can be installed by running the following commands:
$ sudo add-apt-repository ppa:ethereum/ethereum
$ sudo apt-get update
To install solc
on macOS, execute the following commands:
$ brew tap ethereum/ethereum
This command will add the Ethereum repository to the list of brew
formulas:
$ brew install solidity
This command will produce a long output and may take a few minutes to complete. If there are no errors produced, then eventually it will install Solidity.
In order to verify that the Solidity compiler is installed and to validate the version of the compiler, the following command can be used:
$ solc --version
This command will produce the output shown as follows, displaying the version of the Solidity compiler.
solc, the solidity compiler commandline interface
Version: 0.6.1+commit.e6f7d5a4.Darwin.appleclang
This output confirms that the Solidity compiler is installed successfully.
solc
supports a variety of functions. A few examples are shown as follows:
As an example, we'll use a simple contract, Addition.sol
:
pragma solidity ^0.5.0;
contract Addition
{
uint8 x;
function addx(uint8 y, uint8 z ) public
{
x = y + z;
}
function retrievex() view public returns (uint8)
{
return x;
}
}
In order to see the smart contract in compiled binary format, we can use the following command:
$ solc --bin Addition.sol
This command will produce an output similar to the following:
======= Addition.sol:Addition =======
Binary:
608060405234801561001057600080fd5b50610100806100206000396000f3fe60806 04052348015600f57600080fd5b506004361060325760003560e01c806336718d8014 6037578063ac04e0a0146072575b600080fd5b607060048036036040811015604b576 00080fd5b81019080803560ff169060200190929190803560ff169060200190929190 5050506094565b005b607860b4565b604051808260ff1660ff1681526020019150506 0405180910390f35b8082016000806101000a81548160ff021916908360ff16021790 55505050565b60008060009054906101000a900460ff1690509056fea264697066735 822122020bae3e7dea36338ad4ced23dee3370621e38b08377e773854fcc1b2226092 4d64736f6c63430006010033
This output shows the binary translation of the Addition.sol
contract code represented in hex.
As a gas fee is charged for every operation that the EVM executes, it's a good practice to estimate gas before deploying a contract on a live network. Estimating gas gives a good approximation of how much gas will be consumed by the operations specified in the contract code, which gives an indication of how much ether is required to be spent in order to run a contract. We can use the --gas
flag for this purpose, as shown in the following example:
$ solc --gas Addition.sol
This will give the following output:
======= Addition.sol:Addition =======
Gas estimation:
construction:
99 + 51200 = 51299
external:
addx(uint8,uint8): 21120
retrievex(): 1061
This output shows how much gas usage is expected for these operations in the Addition.sol
smart contract. The gas estimation is shown next to each function. For example, the addx()
function is expected to use 21120
gas.
We can generate the Application Binary Interface (ABI) using solc
, which is a standard way to interact with the contracts:
$ solc --abi Addition.sol
This command will produce a file named Addition.abi
as output. The following are the contents of the output file Addition.abi
:
======= Addition.sol:Addition =======
Contract JSON ABI
[{"inputs":[{"internalType":"uint8","name":"y","type":"uint8"},{"inte rnalType":"uint8","name":"z","type":"uint8"}],"name":"addx","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"n ame":"retrievex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]
The preceding output displays the contents of the Addition.abi
file, which are formatted in JSON style. It consists of inputs and outputs along with their types. We will generate and use ABIs later in this chapter to interact with the deployed smart contracts.
Another useful command to compile and produce a binary compiled file along with an ABI is shown here:
$ solc --bin --abi -o bin Addition.sol
The message displays if the compiler run is successful, otherwise errors are reported.
Compiler run successful. Artifact(s) can be found in directory bin.
This command will produce a message and two files in the output directory bin
:
Addition.abi
: This contains the ABI of the smart contract in JSON format.Addition.bin
: This contains the hex representation of binary of the smart contract code.The output of both files is shown in the following screenshot:
Figure 14.2: ABI and binary output of the solidity compiler
The ABI encodes information about smart contracts' functions and events. It acts as an interface between EVM-level bytecode and high-level smart contract program code. To interact with a smart contract deployed on the Ethereum blockchain, external programs require an ABI and the address of the smart contract.
solc
is a very powerful command and further options can be explored using the --help
flag, which will display detailed options. However, the preceding commands used for compilation, ABI generation, and gas estimation should be sufficient for most development and deployment requirements.
There are various tools and libraries available for Ethereum. The most common ones are discussed here. In this section, we will first install the prerequisites that are required for developing applications for Ethereum.
The first requirement is Node.js, which we'll install next.
As Node.js is required for most of the tools and libraries, it can be installed using the following commands.
Node.js can either be installed directly from the website or by using bash
:
$ curl "https://nodejs.org/dist/latest/node-${VERSION:-$(wget -qO- https://nodejs.org/dist/latest/ | sed -nE 's|.*>node-(.*).pkg</a>.*|1|p')}.pkg" > "$HOME/Downloads/node-latest.pkg" && sudo installer -store -pkg "$HOME/Downloads/node-latest.pkg" -target "/"
Alternatively, Node.js can be installed using brew
on macOS:
$ brew install node
Or, go to https://nodejs.org/en/download/ and install Node.js for your operating system, as shown in the following screenshot:
Figure 14.3: Downloads available for Node.js
At times, it is not possible to test on the testnet, and the mainnet is obviously not a place to test contracts. A private network can be time-consuming to set up at times. Ganache CLI (formerly EthereumJS) comes in handy when quick testing is required and no testnet is available. It simulates the Ethereum geth
client behavior and allows faster development testing. Ganache CLI is available via npm
as a Node.js package.
Before installing TestRPC, Node.js should already have been installed and the npm
package manager should also be available.
ganache-cli
can be installed using this command:
$ npm install -g ganache-cli
In order to start ganache-cli
, simply issue this command, keep it running in the background, and open another terminal to work on contracts:
$ ganache-cli
When TestRPC runs, it will display an output similar to the one shown in the following screenshot. It will automatically generate 10 accounts and private keys, along with an HD wallet. It will start to listen for incoming connections on TCP port 8545
:
Figure 14.4: Ganache CLI
ganache-cli
has a number of flags to customize the simulated chain according to your requirements. For example, flag -a
allows you to specify the number of accounts to generate at startup. Similarly -b
allows the user to configure block time for mining.
Detailed help is available using the following command:
$ ganache-cli --help
Ganache CLI is a command-line tool. However, at times, it is desirable to have a fully featured tool with a rich graphical user interface (GUI). A GUI equivalent of Ganache CLI is Ganache, which we will introduce next.
Ganache is a notable addition to the ever-growing set of development tools and libraries developed for Ethereum. This is a simulated personal blockchain with a user-friendly GUI to view transactions, blocks, and relevant details. This is a fully working personal blockchain that supports the simulation of a number of different hard forks, such as Homestead, Byzantium, Istanbul, or Petersburg.
Ganache can be downloaded from https://www.trufflesuite.com/ganache.
Ganache is based on a JavaScript implementation of the Ethereum blockchain, with built-in block explorer and mining, making testing locally on the system very easy. As shown in the following screenshot, you can view transactions, blocks, and addresses in detail on the frontend:
Figure 14.5: Ganache, a personal Ethereum blockchain
When you start Ganache for the first time, it will ask whether the user wants to create a quick blockchain or create a new workspace that can be saved, and it also has advanced setup options:
Figure 14.6: Creating a workspace
Select the NEW WORKSPACE or QUICKSTART option as required. We will choose NEW WORKSPACE as we want to explore more advanced features. For a quicker temporary setup with default options, which could be useful for simple testing, you can choose QUICKSTART.
If NEW WORKSPACE is selected, there are a number of options available to configure the blockchain. The configuration options are WORKSPACE NAME, where you can specify a name for your project. Additionally, Truffle projects can also be added here—we will cover Truffle in more detail later in the chapter:
Figure 14.7: Workspace in Ganache
Other options include SERVER, ACCOUNTS & KEYS, CHAIN, and ADVANCED.
The SERVER tab is used to configure RPC connectivity by specifying the hostname, port number, and network ID:
Figure 14.8: Server configuration
ACCOUNTS & KEYS provides options to configure balance and the number of accounts to generate. The CHAIN option provides a configuration interface for specifying the gas limit, gas price, and hard fork, which is required to be simulated, such as Byzantine or Petersburg: :
Figure 14.9: Chain configuration
The ADVANCED option is available to configure logging and analytics-related settings. Once you have all the configuration options set, save the workspace by selecting SAVE WORKSPACE, and the main transaction view of the Ganache personal blockchain will show:
Figure 14.10: Ganache main view
With this, we conclude our introduction to Ganache, a mainstream tool used in blockchain development. Now we will move on to different development frameworks that are available for Ethereum.
There are a number of other notable frameworks available for Ethereum development. It is almost impossible to cover all of them, but an introduction to some of the mainstream frameworks and tools is given as follows.
Truffle (available at https://www.trufflesuite.com) is a development environment that makes it easier and simpler to test and deploy Ethereum contracts. Truffle provides contract compilation and linking along with an automated testing framework using Mocha and Chai. It also makes it easier to deploy the contracts to any privatenet, public, or testnet Ethereum blockchain. Also, an asset pipeline is provided, which makes it easier for all JavaScript files to be processed, making them ready for use by a browser.
Before installation, it is assumed that node
is available, which can be queried as shown here. If node
is not available, then the installation of node
is required first in order to install truffle
:
$ node --version
V12.14.1
The installation of truffle
is very simple and can be done using the following command via Node Package Manager (npm):
$ npm install truffle –g
This will take a few minutes; once it is installed, the truffle
command can be used to display help information and verify that it is installed correctly:
/us/local/bin/truffle -> /usr/local/lib/node_modules/truffle/build/cli.bundled.js
/usr/local/lib
└── [email protected]
Type truffle
in the Terminal to display usage help:
$ truffle
This will display the following output:
Figure 14.11: Truffle help
Alternatively, the repository is available at https://github.com/trufflesuite/truffle, which can be cloned locally to install truffle
. Git can be used to clone the repository using the following command:
$ git clone https://github.com/trufflesuite/truffle.git
We will use Truffle later in Chapter 15, Introducing Web3, to test and deploy smart contracts on the Ethereum blockchain. For now, we'll continue to explore some of the frameworks used for development on the Ethereum blockchain.
Web User Interface (UI) development is an important part of DApp development. As such, many web techniques and tools, ranging from simple HTML and JavaScript to advanced frameworks such as Redux and React, are used to develop web UIs for DApps.
We will cover basic JavaScript- and HTML-based UIs, along with an advanced framework called Drizzle, in the upcoming chapters.
Drizzle is a collection of frontend libraries that allows the easy development of web UIs for decentralized applications. It is based on the Redux store and allows seamless synchronization of contract and transaction data.
Drizzle is installed using the following command:
$ npm install drizzle –save
In Chapter 15, Introducing Web3, we will explore Drizzle in detail and use it in some examples.
Embark is a complete and powerful developer platform for building and deploying decentralization applications. It is used for smart contract development, configuration, testing, and deployment. It also integrates with Swarm, IPFS, and Whisper. There is also a web interface called Cockpit available with Embark, which provides an integrated development environment for easy development and debugging of decentralized applications.
Embark can be installed by using the following command:
$ npm install -g embark
More details on Embark are available on the official website at https://framework.embarklabs.io.
Brownie is a Python-based framework for Ethereum smart contract development and testing. It has the full support of Solidity and Vyper with relevant testing and debugging tools. More information is available at https://eth-brownie.readthedocs.io/en/stable/.
Waffle is a framework for smart contract development and testing. It is claimed to be faster than Truffle. More details are available on the official website at https://getwaffle.io.
This framework allows DApp development, debugging, testing, and testing in Solidity and Vyper. It is based on Ether.Js
(https://github.com/ethers-io/ethers.js/).
This toolkit has a rich set of tools that allow easy smart contract development. It supports compiling, deploying, upgrading, and interacting with smart contracts. Further information is available here: https://openzeppelin.com/sdk/.
In this section, we have covered some of the mainstream frameworks that are used in the Ethereum ecosystem for development. In the next section, we will explore which tools are available for writing and deploying smart contracts.
There are various steps that need to be taken in order to develop and deploy the contracts. Broadly, these can be divided into three steps: writing, testing, and deployment. After deployment, the next optional step is to create the UI and present it to the end users via a web server. A web interface is sometimes not needed in the contracts where no human input or monitoring is required, but usually there is a requirement to create a web interface so that end users can interact with the contract using familiar web-based interfaces.
The writing step is concerned with writing the contract source code in Solidity. This can be done in any text editor. There are various plugins and add-ons available for Vim in Linux, Atom, and other editors that provide syntax highlighting and formatting for Solidity source code.
Visual Studio Code has become quite popular and is used commonly for Solidity development. There is a Solidity plugin available that allows syntax highlighting, formatting, and IntelliSense.
It can be installed via the Extensions option in Visual Studio Code:
Figure 14.12: Visual Studio Code
The preceding screenshot displays a Visual Studio Code window that comprises a file explorer on the left-hand side and a code editor window. Due to syntax highlighting and IntelliSense being enabled by the Solidity plugin, it becomes easy to write smart contract code. The Solidity plugin for Visual Studio is available in the Visual Studio marketplace at https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity.
Testing is usually performed by automated means. Earlier in the chapter, you were introduced to Truffle, which uses the Mocha framework to test contracts. However, manual functional testing can be performed as well by using the Remix IDE, which was discussed in Chapter 13, Ethereum Development Environment, and running functions manually and validating results. We will cover this in Chapter 15, Introducing Web3.
Once the contract is verified, working, and tested on a simulated environment (for example, Ganache) or on a privatenet, it can be deployed to a public testnet such as Ropsten and eventually to the live blockchain (mainnet).
We will cover all these steps, including verification, development, and creating a web interface, in the next chapter, Chapter 15, Introducing Web3.
Now that we have covered which tools can be used to write Solidity smart contracts, we will introduce the Solidity language. This will be a brief introduction to Solidity, which should provide the base knowledge required to write smart contracts. The syntax of the language is very similar to C and JavaScript, and it is quite easy to program. We'll start by exploring what a smart contract written in the Solidity language looks like.
In the following subsections, we will look at the components of a Solidity source code file, which is important to cover before we move on to writing smart contracts in the next section.
In order to address compatibility issues that may arise from future versions of the solc
version, pragma
can be used to specify the version of the compatible compiler as in the following example:
pragma solidity ^0.5.0
This will ensure that the source file does not compile with versions lower than 0.5.0
and versions starting from 0.6.0
.
Import in Solidity allows the importing of symbols from the existing Solidity files into the current global scope. This is similar to import
statements available in JavaScript, as in the following:, for example:
import "module-name";
Comments can be added to the Solidity source code file in a manner similar to the C language. Multiple-line comments are enclosed in /*
and */
, whereas single-line comments start with //
.
An example solidity
program is as follows, showing the use of pragma
, import
, and comments:
Figure 14.13: Sample Solidity program as shown in the Remix IDE
In this section, we examined what Solidity code of a smart contract looks like. Now it's time to learn about the Solidity language.
Solidity is a domain-specific language of choice for programming contracts in Ethereum. There are other languages that can be used, such as Serpent, Mutan, and LLL, but Solidity is the most popular. Its syntax is closer to both JavaScript and C.
Solidity has evolved into a mature language over the last few years and is quite easy to use, but it still has a long way to go before it can become advanced, standardized, and feature-rich, like other well-established languages such as Java, C, or C#. Nevertheless, this is the most widely used language currently available for programming contracts.
It is a statically typed language, which means that variable type checking in Solidity is carried out at compile time. Each variable, either state or local, must be specified with a type at compile time. This is beneficial in the sense that any validation and checking is completed at compile time and certain types of bugs, such as the interpretation of data types, can be caught earlier in the development cycle instead of at runtime, which could be costly, especially in the case of the blockchain/smart contract paradigm. Other features of the language include inheritance, libraries, and the ability to define composite data types.
Solidity is also called a contract-oriented language. In Solidity, contracts are equivalent to the concept of classes in other object-oriented programming languages.
Just like any programming language, variables in Solidity are the named memory locations that hold values in a program. There are three types of variables in Solidity: local variables, global variables, and state variables.
These variables have a scope limited to only within the function they are declared in. In other words, their values are present only during the execution of the function in which they are declared.
These variables are available globally as they exist in the global namespace. They are used for performing various functions such as ABI encoding, cryptographic functions, and querying blockchain and transaction information.
Solidity provides a number of global variables that are always available in the global namespace. These variables provide information about blocks and transactions. Additionally, cryptographic functions, ABI encoding/decoding, and address-related variables are available. A subset of available variables is shown as follows.
This function is used to compute the Keccak-256 hash of the argument provided to the function:
keccak256(...) returns (bytes32)
This function returns the associated address of the public key from the elliptic curve signature:
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
This returns the current block number:
block.number
This returns the gas price of the transaction:
tx.gasprice (uint)
There are a number of other global variables available. A comprehensive list and details can be found in Solidity's official documentation: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html.
State variables have their values permanently stored in smart contract storage. State variables are declared outside the body of a function, and they remain available throughout the contract depending on the accessibility assigned to them and as long as the contract persists:
pragma solidity >=0.5.0;
contract Addition {
uint x; // State variable
}
Here, x
is a state variable whose value will be stored in contract storage.
There are three types of state variables, based on their visibility scope
this
keyword. They can also be called from other contracts and transactions. A getter
function is automatically created for all public variables.In the next section, we will introduce the data types supported in Solidity.
Solidity has two categories of data types: value types and reference types.
Value types are variables that are always passed by a value. This means that value types hold their value or data directly, allowing a variable's value held in memory to be directly accessible by accessing the variable.
Reference types store the address of the memory location where the value is stored. This is in contrast with value types, which store the actual value of a variable directly with the variable itself. When using reference types, it is essential to explicitly specify the storage area where the type is stored, for example, memory, storage, or calldata.
Remember that EVM can read and write data in different locations.
- Stack: This is a temporary storage area where EVM opcodes pop and push data during execution of the bytecode.
- Calldata: This is a read-only location that holds the data field of a transaction. It holds function execution data and parameters.
- Memory: This is a temporary memory location that is available to functions of the smart contract during their execution. As soon as the execution of the transaction finishes, this memory is cleared.
- Storage: This is the persistent global memory available to all functions in a smart contract. State variables use storage.
- Code: This is where the code that is executing is stored. This can also be used as a static data storage location.
- Logs: This is the area where the output of the events emitting from the smart contracts is stored.
The specific location used for storing values of a variable depends on the data type of the variable and where the variable has been declared. For example, function parameter variables are stored in memory, whereas state variables are stored in storage.
Now we'll describe value types in detail.
Value types mainly include Booleans, integers, addresses, and literals, which are explained in detail here.
This data type has two possible values, true
or false
, for example:
bool v = true;
or
bool v = false;
This statement assigns the value true
or false
to v
depending on the assignment.
This data type represents integers. The following table shows various keywords used to declare integer data types:
Keyword |
Types |
Details |
|
Signed integer |
|
|
Unsigned integer |
|
For example, in this code, note that uint
is an alias for uint256
:
uint256 x;
uint y;
uint256 z;
These types can also be declared with the constant
keyword, which means that no storage slot will be reserved by the compiler for these variables. In this case, each occurrence will be replaced with the actual value:
uint constant z=10+10;
This data type holds a 160-bit long (20 byte) value. This type has several members that can be used to interact with and query the contracts. These members are described here:
balance
member returns the balance of the address in Wei.true
or false
depending on the result of the transaction, for example, the following:
address to = 0x6414cc08d148dce9ebf5a2d0b7c220ed2d3203da;
address from = this;
if (to.balance < 10 && from.balance > 50) to.send(20);
call
, callcode
, and delegatecall
functions are provided in order to interact with functions that do not have an ABI. These functions should be used with caution as they are not safe to use due to the impact on type safety and security of the contracts.bytes1
to bytes32
, whereas dynamically sized keywords include bytes
and string
. The bytes
keyword is used for raw byte data, and string
is used for strings encoded in UTF-8. As these arrays are returned by the value, calling them will incur a gas cost. length
is a member of array value types and returns the length of the byte array.
These are used to represent a fixed value. There are different types of literals that are described as follows:
uint8 x = 2;
'packt' "packt"
hex
and specified within double or single quotation marks. An example is shown as follows:
(hex'AABBCC');
enum Order {Filled, Placed, Expired };
Order private ord;
ord=Order.Filled;
Explicit conversion to and from all integer types is allowed with enums.
As the name suggests, these types are passed by reference and are discussed in the following section. These are also known as complex types. Reference types include arrays, structs, and mappings.
Arrays represent a contiguous set of elements of the same size and type laid out at a memory location. The concept is the same as any other programming language. Arrays have two members, named length
and push
.
These constructs can be used to group a set of dissimilar data types under a logical group. These can be used to define new custom types, as shown in the following example:
pragma solidity ^0.4.0;
contract TestStruct {
struct Trade
{
uint tradeid;
uint quantity;
uint price;
string trader;
}
//This struct can be initialized and used as below
Trade tStruct = Trade({tradeid:123, quantity:1, price:1, trader:"equinox"});
}
In the preceding code, we declared a struct
named Trade
that has four fields. tradeid
, quantity
, and price
are of the uint
type, whereas trader
is of the string
type. Once the struct
is declared, we can initialize and use it. We initialize it by using Trade tStruct
and assigning 123
to tradeid
, 1
to quantity
, and "equinox"
to trader
.
Sometimes it is desirable to choose the location of the variable data storage. This choice allows for better gas expense management. We can use the data location name to specify where a particular complex data type will be stored. Depending on the default or annotation specified, the location can be storage
, memory
, or calldata
. This is applicable to arrays and structs and can be specified using the storage
or memory
keywords. calldata
behaves almost like memory. It is an unmodifiable and temporary area that can be used to store function arguments.
For example, in the preceding structs example, if we want to use only memory (temporarily) we can do that by using the memory
keyword when using the structure and assigning values to fields in the struct
, as shown here:
Trade memory tStruct;
tStruct.tradeid = 123;
As copying between memory and storage can be quite expensive, specifying a location can be helpful to control the gas expenditure at times.
Parameters of external functions use calldata memory. By default, parameters of functions are stored in memory, whereas all other local variables make use of storage. State variables, on the other hand, are required to use storage.
Mappings are used for a key-to-value mapping. This is a way to associate a value with a key. All values in this map are already initialized with all zeroes, as in the following for example:
mapping (address => uint) offers;
This example shows that offers
is declared as a mapping. Another example makes this clearer:
mapping (string => uint) bids;
bids["packt"] = 10;
This is basically a dictionary or a hash table, where string values are mapped to integer values. The mapping named bids
has the string packt
mapped to value 10
.
The control structures available in the Solidity language are if...else
, do
, while
, for
, break
, continue
, and return
. They work exactly the same as other languages, such as the C language or JavaScript.
Some examples are shown here:
if
: If x
is equal to 0
, then assign value 0
to y
, else assign 1
to z
:
if (x == 0)
y = 0;
else
z = 1;
do
: Increment x
while z
is greater than 1
:
do{
x++;
} (while z>1);
while
: Increment z
while x
is greater than 0
:
while(x > 0){
z++;
}
for
, break
, and continue
: Perform some work until x
is less than or equal to 10
. This for
loop will run 10
times; if z
is 5
, then break the for
loop:
for(uint8 x=0; x<=10; x++)
{
//perform some work
z++
if(z == 5) break;
}
It will continue the work in a similar vein, but when the condition is met, the loop will start again.
return
: return
is used to stop the execution of a function and returns an optional value. For example:
return 0;
It will stop the execution and return a value of 0
.
Events in Solidity can be used to log certain events in EVM logs. These are quite useful when external interfaces are required to be notified of any change or event in the contract. These logs are stored on the blockchain in transaction logs. Logs cannot be accessed from the contracts but are used as a mechanism to notify change of state or the occurrence of an event (meeting a condition) in the contract.
In a simple example here, the valueEvent
event will return true
if the x
parameter passed to the function Matcher
is equal to or greater than 10
:
pragma solidity >=0.6.0;
contract valueChecker
{
uint8 price=10;
event valueEvent(bool returnValue);
function Matcher(uint8 x) public returns (bool)
{
if (x>=price)
{
emit valueEvent(true);
return true;
}
}
}
Inheritance is supported in Solidity. The is
keyword is used to derive a contract from another contract. In the following example, valueChecker2
is derived from the valueChecker
contract. The derived contract has access to all non-private members of the parent contract:
pragma solidity >=0.6.0;
contract valueChecker
{
uint8 price = 20;
event valueEvent(bool returnValue);
function Matcher(uint8 x) public returns (bool)
{
if (x>=price)
{
emit valueEvent(true);
return true;
}
}
}
contract valueChecker2 is valueChecker
{
function Matcher2() public view returns (uint)
{
return price+10;
}
}
In the preceding example, if the uint8 price = 20
is changed to uint8 private price = 20
, then it will not be accessible by the valueChecker2
contract. This is because now the member is declared as private
, and thus it is not allowed to be accessed by any other contract. The error message that you will see when attempting to compile this contract is as follows:
browser/valuechecker.sol:20:8: DeclarationError: Undeclared identifier.
return price+10;
^---^
Libraries are deployed only once at a specific address and their code is called via the CALLCODE
or DELEGATECALL
opcode of the EVM. The key idea behind libraries is code reusability. They are similar to contracts and act as base contracts to the calling contracts. A library can be declared as shown in the following example:
library Addition
{
function Add(uint x,uint y) returns (uint z)
{
return x + y;
}
}
This library can then be called in the contract, as shown here. First, it needs to be imported and then it can be used anywhere in the code. A simple example is shown as follows:
import "Addition.sol"
function Addtwovalues() returns(uint)
{
return Addition.Add(100,100);
}
There are a few limitations with libraries; for example, they cannot have state variables and cannot inherit or be inherited. Moreover, they cannot receive ether either; this is in contrast to contracts, which can receive ether.
Functions are pieces of code within a smart contract. For example, look at the following code block:
pragma solidity >5.0.0;
contract Test1
{
uint x=2;
function addition1() public view returns (uint y)
{
y=x+2;
}
}
In the preceding code example, with contract Test1
, we have defined a function called addition1()
, which returns an unsigned integer after adding 2
to the value supplied via the variable x
, initialized just before the function.
In this case, 2
is supplied via variable x
, and the function will return 4
by adding 2
to the value of x
. It is a simple function, but demonstrates how functions work and what their different elements are.
There are two function types: internal and external functions.
A function in Solidity can be marked as a constant. Constant functions cannot change anything in the contract; they only return values when they are invoked and do not cost any gas. This is the practical implementation of the concept of call as discussed in the previous chapter.
Note that the state mutability modifier constant
was removed in Solidity version 0.5.0. Instead, use the view
or pure
modifier if you are using Solidity version 0.5.0 or greater.
The syntax to declare a function is shown as follows:
function <nameofthefunction> (<parameter types> <name of the variable>)
{internal|external} [state mutability modifier] [returns (<return types> <name of the variable>)]
For example:
function addition1() public view returns (uint y)
Here, function
is the keyword used to declare the function, addition
is the name of the function, public
is the visibility modifier, view
is the state mutability modifier, returns
is the keyword to specify what is returned from the function, and uint
is the return type with the variable name y
.
Functions in Solidity are modules of code that are associated with a contract. Functions are declared with a name, optional parameters, access modifiers, state mutability modifiers, and an optional return type. This is shown in the following example:
function orderMatcher (uint x)
private view returns (bool return value)
In the preceding code, function
is the keyword used to declare the function. orderMatcher
is the function name, uint x
is an optional parameter, private
is the access modifier or specifier that controls access to the function from external contracts, view
is an optional keyword used to specify that this function does not change anything in the contract but is used only to retrieve values from the contract, and returns (bool return value)
is the optional return type of the function. After this introduction to functions in Solidity, let's consider some of their key elements, parameters, and modifiers:
function <name of the function>(<parameters>) <visibility specifier> <state mutability modifier> returns
(<return data type> <name of the variable>)
{
<function body>
}
f9d55e21
is the first four bytes of the 32-byte Keccak-256 hash of the function named Matcher
:
Figure 14.14: Function hash as shown in the Remix IDE
In this example function, Matcher
has the signature hash of d99c89cb
. This information is useful in order to build interfaces.
<data type> <parameter name>
. This example clarifies the concept where uint x
and uint y
are input parameters of the checkValues
function:
contract myContract
{
function checkValues(uint x, uint y)
{
}
}
<data type> <parameter name>
. This example shows a simple function returning a uint
value:
contract myContract
{
function getValue() returns (uint z)
{
z=x+y;
}
}
A function can return multiple values as well as take multiple inputs. In the preceding example function, getValue
only returns one value, but a function can return up to 14 values of different data types. The names of the unused return parameters can optionally be omitted. An example of such a function could be:
pragma solidity ^0.5.0;
contract Test1
{
function addition1(uint x, uint y) public pure returns (uint z, uint a)
{
z= x+y ;
a=x+y;
return (z,a);
}
}
Here when the code runs, it will take two parameters as input x
and y
, add both, and then assign them to z
and a
, and finally return z
and a
. For example, if we provide 1 and 1 for x
and y
, respectively, then when the variables z
and a
are returned by the function, both will contain 2 as the result.
JUMP
calls at the EVM bytecode level.this
keyword, it is also considered an external call. The this
variable is a pointer that refers to the current contract. It is explicitly convertible to an address and all members of a contract are inherited from the address.The payable is required; otherwise, this function will not be able to receive any ether. This function can be called using the address.call()
method as, for example, in the following:
function ()
{
throw;
}
In this case, if the fallback function is called according to the conditions described earlier; it will call throw
, which will roll back the state to what it was before making the call. It can also be some other construct than throw
; for example, it can log an event that can be used as an alert to feed back the outcome of the call to the calling application.
_
(underscore) is used in the modifier functions that will be replaced with the actual body of the function when the modifier is called. Basically, it symbolizes the function that needs to be guarded. This concept is similar to guard functions in other languages.this
keyword is used.Now we've considered some of the defining features of functions, let's consider how Solidity approaches handling errors.
Solidity provides various functions for error handling. By default, in Solidity, whenever an error occurs, the state does not change and reverts back to the original state.
Some constructs and convenience functions that are available for error handling in Solidity are introduced as follows:
This completes a brief introduction to the Solidity language. The language is very rich and under constant improvement. Detailed documentation and coding guidelines are available online at http://solidity.readthedocs.io/en/latest/.
This chapter started with the introduction of development tools for Ethereum, such as Ganache CLI. The installation of Node.js was also introduced, as most of the tools are JavaScript- and Node.js-based. Then we discussed some frameworks such as Truffle, along with local blockchain solutions for development and testing, such as Ganache, and Drizzle. We also introduced Solidity in this chapter and explored different concepts, such as value types, reference types, functions, and error handling concepts. We also learned how to write contracts using Solidity.
In the next chapter, we will explore the topic of Web3, a JavaScript API that is used to communicate with the Ethereum blockchain.