Solidity Programming for Ethereum Blockchain Smart Contracts
It is no news that Blockchain and Web3 are the raves of the moment. Given this, developers in different fields are migrating into the blockchain ecosystem in their numbers. Ethereum, being the most popular Blockchain and also the first to allow the development of software or programs on the Blockchain known as “Smart Contracts,” requires development in a contract-purpose programming language known as Solidity.
This article introduces smart contracts, Solidity programming language syntax, development environments, writing smart contracts with Solidity, and vulnerabilities in smart contracts. You will also build and deploy your first smart contract on the Ethereum network.
What is Ethereum?
Ethereum is the first blockchain to allow developers to build smart contracts. Vitalik Buterin proposed it in 2013. As an open-source project, it is a platform allowing anyone to build separate or related software. Ethereum came after the Bitcoin blockchain; hence, it makes on the weaknesses of Bitcoin.
For example, the transaction time on the Ethereum chain is in seconds instead of minutes on the Bitcoin chain. Also, while Bitcoin adopts the Proof-of-Work consensus mechanism, which is resource-inefficient, Ethereum is tethered to the Proof-of-Stake tool.
Smart Contracts – What’s Smart About Them?
Smart contracts are computer software or a transaction protocol intended to automatically execute, control, or document legally relevant events and actions according to a contract or an agreement. They are considered smart due to their self-executing nature.
Choosing a Language to Author a Smart Contract
Solidity is the most popular language for authoring smart contracts. However, other Ethereum-specific languages are compatible with the Ethereum Virtual Machine. They include:
- Serpent
- Vyper (a close competitor to Solidity)
- Bamboo
- LLL
The EVM is rigid, hence the importance of sticking with languages specifically built for smart contracts. Why were they built in the first place? Because it’s relatively easy to build a specific-purpose language from scratch compared to hammering and bending a general-purpose language to suit the Ethereum Virtual Machine’s (EVM) needs.
If you want to use a high-level, general-purpose language for smart contracts, you must choose between Golang and Rust. Reasons include Rust being the language for the Parity client and Golang, the Geth client.
Introduction of Solidity
The Solidity programming language is an object-oriented programming language influenced by C++, JavaScript, and Python and has its own rules, otherwise called ‘syntax’ by most developers. However, picking up Solidity is a challenge, especially for those without prior knowledge of Object-Oriented Programming Languages such as JavaScript.
This article will introduce you to the Solidity programming language. In addition, this article will discuss Solidity syntax, its data types, variable types, and functions before moving on to writing Smart contracts with solidity.
Ethereum Virtual Machine
The Ethereum Virtual Machine, henceforth called EVM, is the target platform of Solidity. The Ethereum blockchain allows the possibility of extending its functionality by allowing developers to build and deploy Smart Contracts written as instructions in bytecode.
This means we must convert Solidity source code into bytecode before deploying it into the chain. This conversion is done by a compiler called ‘solc‘.
Diagrammatically, the sequence goes as shown in the code below:
“`mermaid
graph LR
A[Solidity Source Code] -- Goes To --> B(Solidity Compiler)
B --> C((ByteCode))
C --> D{Deployed on EVM}
```
Solidity Characters
Different characters serve different purposes in the Solidity programming language. Below, find a list of characters and their usage:
- We use the “^” character while declaring the version to specify the latest version within a major version. Hence, “^0.8.0” means the latest version within build number 8.
- It does not need a mention for developers but just for any newbie; the ‘//’ character is for comments inside the code. It specifies the licensing, explains what a block of code will do, or describes a particular step in a function.
- As in JavaScript, the semicolon “;” is for ending an instruction. A good habit while debugging a compile error is checking for missing semicolons.
Solidity Constructs
A single Solidity source file would have the following construct declarations:
- State variables
- Structs
- Modifiers
- Event
- Enumeration
- Function
All constructs are usually included in a full-fledged smart contract, with variables and functions as the commonly used constructs.
Solidity Variables
As in other languages, Solidity stores data in variables. The Variables in Solidity are of two types: “State” and “Memory” variables. State variables used in a Smart Contract include any variable outside a function. The memory of a state variable is strong and unchangeable for the whole lifetime of the contract.
Qualifiers often complement state variables. These qualifiers may include:
- Internal qualifier: The Internal qualifier is a default qualifier meaning the state variable is only useful within the current contract and any other that inherits/calls it. The syntax is “int internal varName;”.
- Private qualifier: The private qualifier makes the state variable only useful within the current contract only. The syntax is “int private varName;”.
- Public qualifier: The public qualifier is the qualifier that makes state variables accessible to external Solidity contract files. The syntax is “int public varName;”.
- Constant qualifier: This makes the state variable immutable. We must assign it a value for the Solidity compiler to work with while declaring it. The syntax is “bool constant varName;”.
Structs
Structs in Solidity are similar to structs in C++, Go, and similar languages. They are composite data types that help developers create custom user-defined data types in these languages by allowing the inclusion of several variables of different data types.
You should make use of structs when creating variables with related values, like the identity of a person:
struct Credentials {
string name;
uint age;
bool isLegal;
uint IDNumber;
}
We use the following syntax to create a struct instance:
Person1 = Credentials{"John Doe", 25, true, 40413570};
We often create structs in contract functions, and these can also contain arrays and mapping variables
Modifiers
Modifiers are constructs that change the behavior of a function block. We declare Modifiers using the ‘modifier‘ keyword followed by the identifier function and then the curly brackets {} containing the code. We use an underscore in a modifier to signify the execution of a function block by the compiler. For example, Solidity provides a payable modifier that allows functions to accept Ether.
The syntax for Modifiers is as follows:
modifier usedOn() {
if (msg.Sender == Credentials) {
_;
}
}
Events
Solidity events are similar to events in other languages. Once an event starts, anyone can trap them and execute code in response. They inform the calling application about the present state of the contract and the changes made using the EVM logs functionality.
The syntax of events in Solidity is:
event pullName(address, string);
Enumeration
We declare enumerations by enum keyword and use them to declare custom data types that contain pre-initialized and named constants. They are similar to the “switch/case” in the Go programming language (Golang), where we assign an integer value, starting from 0 to each constant.
Solidity Data Types and Data Locations
The compulsory scaffold for a Solidity source file involves the version and contract declaration. You may also check another article named Writing Ethereum Smart Contracts with Solidity.
Data types in Solidity are a little more than those in a general-purpose programming language. They can either be a value or reference data type. They determine the memory requirements for variables and the types of values stored in them. The most complex variables are integers and bytes. The data types in Solidity include the following:
- bool
- uint/int
- bytes
- address
- mapping
- enum
- struct
- bytes/String
Value types: types that do not take more than 32 bytes of memory — include “bool”, “unit”/”int”, “address”, “byte”, and “enum”. Solidity also supports pass-by-value, where we can reassign variables to another variable or as an argument to a function. Changing the original variable’s value will not affect the value of the other variables.
Reference types that store more than 32 bytes of memory include arrays, structs, strings, and mappings. Mappings are similar to maps in Golang or hash tables and dictionaries in other languages.
Solidity also supports pass-by-reference, where we can reassign a reference type variable to another variable or as an argument to a function. Changing the original variable’s value will result in a change in the other variables.
Variables in Solidity
Variables in Solidity may have one among four different data locations. They include:
- Storage: a global memory accessible by all functions in the Solidity source file.
- Memory: a local memory accessible by all functions in the Solidity source file.
- Calldata: a non-modifiable memory accessible by all incoming functions and function arguments.
- Stack: a working memory for the EVM with 1,24 levels of depth.
The data location of a variable is determined by its data type and its declaration location in the contract.
State variables are stored in the Storage data location, while function parameters and function scope variables are stored in the Memory data location. Arguments supplied by callers are stored in “Calldata” data location, and assignments from a memory variable to another or state variable create a new copy.
Integers
Solidity integers are either signed (“int”) or unsigned (“unit”). Signed integers support positive and negative values, while unsigned integers essentially hold zero and positive values. Writing a Solidity source file reveals many integers to choose from, ranging from 8 to 256. While selecting a range of “unit”, picking only the required set is best. For example, if you want to store values between 0 and 255 or ranging from -128 to 127, use uint8.
The following syntax creates Integers:
uint256 integerName = 500;
Booleans
Solidity Boolean values are either true or false. We can use Boolean values when we expect results to be true or false (1 or 0). These Boolean values are modifiable, and we can use these as incoming and outgoing parameters in functions.
The following solidity syntax declares the Boolean types:
bool isSuccessful = true;
The following example shows how we can use Boolean values as return parameters in solidity functions:
pragma solidity >= 0.7.0 < 0.9.0;
contract ExplainBooleans {
bool isSuccessful = true;
function understandBool() returns (bool) {
isSuccessful = false;
return isSuccessful;
}
}
Bytes
Bytes are 8-bit signed integers that store data in binary format, i.e., in 0’s and 1’s. We can assign hexadecimal values, positive and negative integer values, and even character values to Bytes..
Bitwise operations such as ‘AND,’ ‘OR,’ and ‘NOT‘ can be performed on bytes.
Solidity Functions
Like in every other language, functions are the crucial components of a Solidity source file. They are executed for smart contracts to behave as expected. They receive parameters, execute logic, and return values to the calling function optionally.
Functions can also be anonymous in C++ and Go, but only one anonymous function is allowed in a Solidity file, called the fallback function.
We declare a function using the ‘function‘ keyword, followed by the function name, the input parameters in brackets, the name of a modifier, and the variable(s) to be returned. The syntax for function declaration in Solidity is:
function getName (string personName) {
// code here
}
Functions have qualifiers similar to state variables. They include:
- Internal qualifier: A default qualifier means the function is only useful within the current contract and any other that inherits/calls it.
- External qualifier: The external qualifier means that we can use the function externally but not internally. They become a part of the contract’s interface.
- Private qualifier: This makes the state variable only useful within the current contract only.
- Public qualifier: This qualifier makes state variables accessible to external Solidity contract files.
- Constant qualifier: They cannot modify the state of the Blockchain. Functions declared with the constant qualifier cannot modify variables, trap events, create other contracts, etc. They can only read and return the current state of data.
- View qualifier: A view qualifier is synonymous with constant functions.
- Pure qualifier: We use pure qualifiers to constrain functions further. Functions declared with the pure qualifier can neither read nor write to the Blockchain. They cannot access state variables.
- Payable qualifier: This allows functions to accept Ether from the caller function.
Solidity Arrays
We use Arrays in Solidity to accept a group or collection of data. The “[]” brackets are used to collect these data in one placeholder. Arrays are normally useful in operations like iterating, searching, sorting, etc.
We can create Arrays in Solidity programming language, as per the syntax below:
uint[5] newArray;
Solidity arrays can be either fixed or dynamic. Fixed arrays are more rigid, with specified sizes set at declaration. Dynamic arrays are like slices in Go, with the property of mutability.
The line of code below shows a fixed array of test scores:
int[5] scores = [int(10), 80, 70, 85, 50];
// or within a function;
int[5] scores;
age = [int(10), 80, 70, 85, 50];
The line of code below shows a dynamic array syntax with and without a new keyword:
int[] scores;
int[] scores = [int(10), 80, 70, 85, 50];
// using the new operator keyword;
int[] scores = new int[](10};
There are special arrays in Solidity: bytes and String arrays. All properties of arrays include:
- ‘index’: for assessing each element in an array.
- ‘push’: for appending new elements to dynamic arrays.
- ‘length’: for addressing the size of an array.
Tools for Authoring Smart Contracts
Setting up your development environment for smart contracts development is not a specific process. The following are tools you may require:
Remix IDE
Remix is a powerful, open-source tool that helps you write Solidity contracts straight from the browser. Written in JavaScript, Remix supports both usage in the browser and locally. Remix also supports testing, debugging, deploying smart contracts, and much more. You can read more about Remix in the official documentation. You can also download Remix here.
2. Truffle Framework
Truffle is a world-class development environment, testing framework, and asset pipeline for blockchains using the Ethereum Virtual Machine (EVM), aiming to make life as a developer easier. You can learn more about Truffle from the official documentation.
3. Embark Framework
Embark is a fast, easy-to-use, and powerful developer environment to build and deploy decentralized applications, also known as “DApps”. It integrates with Ethereum blockchains, decentralized storage like IPFS and Swarm, and decentralized communication platforms like Whisper.
Embark aims to make building decentralized applications as easy as possible by providing all the tools needed and staying extensible simultaneously. You can read further in the official documentation. You can also get Embark here.
4. HardHat
Hardhat is a tool that brings the complete Ethereum development environment to your local environment. It runs Solidity locally and allows you to easily deploy your contracts, debug, and run tests without going live.
Initializing a Smart Contract in Solidity
It is common to print out “Hello World” on Web2. Whoever invented that rule should accept the Decentralization gospel. We will print “Hello Blockchain” in Solidity compared to other languages. We will also learn the basic structure of a contract.
pragma solidity ^0.8.10;
contract HelloWorld {
string public greet = “Hello World!”;
}
Understanding the Above Solidity Code
1. pragma
‘pragma‘ is the compulsory first word in the first line of code within any Solidity file. Pragma is a directive specifying the Solidity compiler version for the current Solidity file.
2. Solidity ^0.8.10;
Solidity is a new language and is subject to continuous improvement on an ongoing basis. Thus, there are older and newer versions, and you could set a range of compiler versions with this syntax:
pragma solidity >= ^0.5.0 < 0.9.0;
This means that any version between that range can run/compile the Solidity source file.
3. contract
As stated earlier, Solidity is created to be EVM-Compatible. It is a contract-specific language. A smart contract is a set of functions that does stuff. Thus, between the contract “Name{ }” tags, there are other properties, including functions, data types, and everything you have in a normal object-oriented language.
4. public
The public keyword signifies functions that anyone can call internally and externally.
Vulnerabilities in Smart Contracts
A research was conducted in 2018, where roughly one million deployed Ethereum Smart Contracts were analyzed. The research uncovered vulnerabilities in these smart contracts. The researchers grouped these vulnerabilities into three for ease of identification:
- Suicidal Contracts: Smart contracts that arbitrary addresses can kill.
- Greedy Contracts: Smart contracts in a state where they can no longer release ether
- Prodigal Contracts: Smart contracts that can release ether to an arbitrary address.
What’s common among these vulnerabilities? They are introduced to smart contracts via code. It could be unintentional by the developer, but regardless, these vulnerabilities can potentially cause an unexpected loss in funds for users. That’s why Vyper was built. It tries to eliminate this by letting users write secure code, making it difficult for programmers to write misleading or vulnerable code accidentally.
Nonetheless, Solidity has been the most adopted language for developing smart contracts for Ethereum, has more community support in terms of resources, and has the most demand in the job market. Hence it is advisable to start with Solidity.
A Simple Storage Contract in Solidity
Let’s look at the steps involved in building a smart contract with Solidity by building a contract ourselves. First, navigate to the Remix IDE on your browser. You can consider writing the source code initially on your local code editor before pasting it into remote Remix IDE.
On Remix, click the file icon to create a new solidity source file named “storage.sol“.
Next, initialize the smart contract with the version and contract syntax as we saw above. After this, input the following code:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 < 0.9.0;
contract simpleStorage {
// variable initialization
uint256 favoriteNumber;
// function definition
function store(uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
}
This simple demo shows you how Remix IDE works and parts of a Solidity contract. Select the second button from the left navigation pane to compile the code if the auto-compiling option is not on. After clicking Compile, click the Deploy button to deploy the smart contract.
After deploying, you will see a success message in the console and a DEPLOY & RUN TRANSACTIONS pane section. Click the toggle icon under the Deployed Contracts section to see our function details, as seen above.
This is how Solidity is used to write and deploy smart contracts. Now, if you input a number in the text field greyed with the uint256 type and click on the Store button beside it, it will automatically store the number as seen in the terminal. Then, click the delete button at the left of the Deployed Contracts section to delete the contract.
Understanding the Above Code
The first line of the code above shows the License adopted. Many popular codebases you will find on GitHub all have a License. The next line is the version specification. This sample used a range of versions, so the contract above is compatible with different versions of Solidity under that range.
Next, we started the contract and created a variable. It is set to “public” to be visible and directly accessed. Finally, we defined a function named store that receives an uint256 data type and is public. The function is also set to “public“, and we simply passed “favoriteNumber” to the function.
Conclusion
This article introduced you to smart contracts. First, you learned about Solidity syntax for writing smart contracts. Next, you see how contracts are initialized in Solidity and what each beginning term stands for.
Afterward, we moved on to why Solidity is the best language to use for authoring smart contracts in Ethereum’s ecosystem. And finally, we saw how a smart contract is written, compiled, and deployed using the online Remix IDE.
MacBobby Chibuzor is a Robotics Hardware Engineer and a Tech Polyglot. He also has practical experience in Software Engineering and Machine Learning, with an interest in embedded systems and Blockchain technology. In addition, Mac loves to spend his free time in technical writing.