I published a beginner-friendly introduction to Solidity programming language a few days ago. Today, we'll be diving a little bit deeper by explaining more basic concepts. If you haven't read the previous post, that would be a great place to start.
Solidity is the most popular programming language used in writing smart contracts for the blockchain. This post aims to provide a soft landing for newbies or experienced devs seeking to transition from web2 to web3. In this post, you'll learn
- Data types - Enum, Mapping, Arrays, and Structs in Solidity
- Iterations - For loops and Do while
- Conditionals - If-Else statement and require function
- Constructors in Solidity
- Build, deploy and interact with a simple Todo program
Prerequisites
In order to follow along, you typically need to set up Solidity on your local machine. Thanks to Remix - An excellent online editor, we can write, deploy and interact with our smart contract without installing anything. Click on open Remix and let's get started.
This post assumes the reader is already caught up with the first part of this tutorial. If you haven't read it, click here to get up to speed.
Enums, Mappings, Arrays and Structs in Solidity
Enums
Enum is short for Enumerable. Enums are user-defined types that use easy-to-remember names for a set of constants. For example, to represent the four cardinal points using an Enum would be:
pragma solidity 0.8.7;
contract MySecondContract {
enum Position {NORTH, EAST, SOUTH, WEST}
}
Behind the scenes, the solidity compiler maps each value with an unsigned integer, with the first value starting at 0. When you specify an enum value, the index will be used during execution. Let us assume we are developing a game with Solidity, we can move a player to different positions using enum like so:
pragma solidity 0.8.7;
contract MySecondContract {
enum Position {NORTH, EAST, SOUTH, WEST}
Position moveUser;
function setUserDirection() public{
moveUser = Position.WEST; // moveUser => 3
}
}
Mappings
Mappings are data types that consist of keys and values. Each key maps to a corresponding value. A mapping can be represented in the form mapping(KeyType => ValueType) VariableName. The keyType can be any value type with the exception of reference types (mapping, arrays, and Structs). The valuetype can be of any type, including reference types. An example of a mapping is
pragma solidity 0.8.7;
contract MySecondContract {
mapping(uint => address) users;
}
The above example, in simple terms, interprets as - A mapping of users; whereby, given a particular user id (integer data type) we return the user’s address (address data type).
Solidity is a strongly typed language, so when you declare the key as an integer, you cannot use a key of a different type to write data to the mapping. This will cause a key type error, same goes for value. It is important to note that you cannot update a mapping outside a function.
pragma solidity 0.8.7;
contract MySecondContract {
mapping(uint => string) users;
users[0] = "The Everyday Dev"; // mapping assigned outside a function will cause an error
function playWithMappings() public {
//To store to a mapping
users[1] = "Nnamdi";
users[2] = "Umeh";
users["3"] = "Solidity is fun"; //This will give key type error
//To retrieve from a mapping
users[1]; // => "Nnamdi;
}
}
Arrays
Arrays in Solidity, are data types that enable the grouping of similar data types under a single variable name. Solidity arrays do not support the grouping of different data types. In Solidity, there are two types of arrays:
Fixed-size are arrays with a fixed length. They are represented in the form Type[length]. Adding extra data after filling up the declared length will result in an error.
Dynamic size are arrays that have a dynamic length. Their lengths are not fixed, and adding data does not cause any error. They are represented in the form of Type[]
pragma solidity 0.8.7;
contract MySecondContract {
uint[3] numbers; //fixed length
string[] names; //dynamic length
function playWithArrays() public {
numbers[0] = 1;
//
numbers[3] = 5; //This will give a length error
names[0] = "Nnamdi";
//
names[500] = "Umeh"; // No error, because it is dynamic length
}
}
Structs
Structs in Solidity are user-defined types that are used to represent a record. Struct enables you to create data types with different properties. A struct can be used inside mappings and arrays. They can also contain arrays and mappings. Let's say you want to create a user profile in your smart contract, a User struct can be created as follows:
pragma solidity 0.8.7;
contract MySecondContract {
enum Language {ENGLISH, FRENCH, GERMAN}
struct User {
uint userId;
string name;
Language locale; //Struct containing enum
}
mapping(uint => User) users; // mapping with struct type
function playWithStructs() public {
//User struct usage
User memory user;
user.userId = 1;
user.name = "Nnamdi Umeh";
user.locale = Language.ENGLISH;
users[1] = user; //saves the user to users map
}
}
Iterations - For loops and Do while
Iterations are an important part of any programming language. In Solidity, we use iterations to read complex data structures like arrays, structs, and mappings. The three iterators in solidity are for-loop, do-while, and while loop.
For-loops
pragma solidity 0.8.7;
contract MySecondContract {
uint8 [3] numbers = [1,2,3];
function sumNumbers() public returns (uint8){
uint8 sum;
for(uint8 i = 0; i < 3; i++){
sum = sum + numbers[i];
}
return sum;
}
}
Solidity checks the counter for overflow before the increment. If you're looping over a known length, it is a good practice to skip the checking by using unchecked. The above for-block can be optimized further
for(uint8 i = 0; i < 3;){
sum = sum + numbers[i];
unchecked{
++i;
}
}
Do-while loop
Do-while loop, although not commonly used, is available Solidity. The above for-loop block is written below. Note that the condition checking is now at the bottom of the block:
pragma solidity 0.8.7;
contract MySecondContract {
uint8 [3] numbers = [1,2,3];
function sumNumbers() public returns (uint8){
uint8 sum;
uint8 i = 0;
do{
sum = sum + numbers[i];
i++;
}while(i < 3);
return sum;
}
}
While-loop
The difference between while loop and Do-while is that Do-while executes at least once, before checking. While loop checks if the condition is satisfied first, before execution.
pragma solidity 0.8.7;
contract MySecondContract {
uint8 [3] numbers = [1,2,3];
function sumNumbers() public view returns (uint8){
uint8 sum;
uint8 i = 0;
while(i < 3){
sum = sum + numbers[i];
i++;
}
return sum;
}
}
Conditionals: IF-ELSE statements and require function
Conditionals are used to control code execution flow. In Solidity, we use conditionals to determine whether an action should take place or not. They're pretty straightforward, so I'll just show you the syntaxes. I hope you make a good choice 🙂
IF-ELSE statements
pragma solidity 0.8.7;
contract MySecondContract {
enum Action {DropALike, DropAComment}
bool isLearningFun = true;
Action readerChoice;
function MakeADecision() public{
if(isLearningFun){
readerChoice = Action.DropALike;
} else {
readerChoice = Action.DropAComment;
}
}
}
Require function
The Require function in Solidity reverts the execution of the code if a set condition is not met. If the condition is set, the code executes normally.
pragma solidity 0.8.7;
contract MySecondContract {
uint8 age = 17;
bool isEligible;
function checkVotingEligibility() public{
require(age >= 18, "Not eligible to vote");
isEligible = true;
}
}
Constructors in Solidity
A constructor is a unique function that executes only once when the contract is created. The constructor is typically used to initialize state variables. Setting the constructor function on a contract is optional.
pragma solidity 0.8.7;
contract MySecondContract {
bool isDeployed;
constructor(){
isDeployed = true;
}
}
Wrapping it up: Create a Todo Dapp
In order to put all we have learned so far in perspective, let's write a simple Todo program. This program will allow the users to create a list of Todos, and also fetch and mark the Todos.
//SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract Play {
//uint private _id;
struct Todo {
uint id;
string task;
bool isCompleted;
address owner;
}
mapping(address => mapping(uint => Todo)) private todos;
mapping(address => uint) private userTodoCount;
function addTodo(string memory _task) public {
uint _id = userTodoCount[msg.sender] + 1;
Todo memory newTodo;
newTodo.id = _id;
newTodo.task = _task;
newTodo.isCompleted = false;
newTodo.owner = msg.sender;
userTodoCount[msg.sender] = _id;
todos[msg.sender][_id] = newTodo;
}
function getUserTodoCount(address user) public view returns(uint){
return userTodoCount[user];
}
function getTodos(address _user) external view returns (Todo[] memory){
uint _todoCount = getUserTodoCount(msg.sender);
Todo[] memory myTodos = new Todo[](_todoCount);
for(uint i = 1; i <= _todoCount; i++) {
myTodos[i-1] = todos[_user][i];
}
return myTodos;
}
function markAsCompleted(uint todoId) external {
Todo storage myTodo = todos[msg.sender][todoId];
require(myTodo.owner == msg.sender, 'Must be owner to update');
myTodo.isCompleted = true;
}
}
Now, you know enough Solidity to get started. Which ideas do you intend to build? Let me know in the comments.
If you enjoyed this post, I intend on making more posts like this on Solidity and web3.
I'll go from Solidity to building interactive web applications that communicate with the blockchain. I also plan on tweeting about web3 stuff. If that's your kind of thing, you can also follow me on Twitter. Thanks for reading!