Solidity Tutorial: Complete Step-by-Step Guide For Beginners 2019

Julien Klepatch

In this Solidity tutorial you are going to learn how to code Solidity smart contracts by developing 5 smart contracts on Ethereum, with step-by-step instructions. This tutorial is for total beginners to Ethereum and Solidity, and does not require to install any development tool. You only need a web browser.

WORD OF CAUTION: Solidity changed a lot since its creation. The most recent version is Solidity 0.5.6. This tutorial covers the 0.5.x versions, i.e the most recent one. Be very careful with other tutorials as many of them teach older versions of Solidity (0.4.x) and are outdated.

Solidity is the most popular language to write Ethereum smart contracts. This is a high-level language that compiles to bytecode for the Ethereum Virtual Machine (EVM). Its syntax is similar to Javascript, but despite this superficial similarity this is a very different language:

  • This is a compiled language, which means you can’t just run it directly in a virtual machine like you do with Javascript. There is a separate compilation step before you can run the code.
  • This is a typed language, which means you need to declare the type of each variable
  • It’s much more limited than Javascript

Despite its shortcomings, that’s the best we have to write smart contracts on Ethereum. If you want to unleash the power of decentralized applications on Ethereum, you have to learn it!

This tutorial is part of Smart contract 30, a full course to learn how to develop Solidity smart contracts. In Smart contract 30, you go from beginner to expert level in Solidity by building 1 smart contract per day during 30 days, for a total of 30 smart contracts. This covers most of the aspects of the Solidity language, as well as many business applications: financial transfers, ERC20 tokens, DAO, governance / voting, private data on blockchain, etc…After you finish this course, you will be a competent Solidity developers, ready to develop smart contract at a professional level. For a full description of the course checkout the curriculum on EatTheBlocks Pro.

This tutorial gives away for free the first 5 Solidity tutorials of this course.

Contract 1: Simple smart contract

The first smart contract of this tutorial will be like its name implies, very simple. The focus will be on the tools and the workflow. We will:

  1. Discover Remix, the IDE for Solidity
  2. Code a Solidity smart contract
  3. Compile the smart contract
  4. Deploy the smart contract on a local blockchain

1. Discover Remix, the IDE for Solidity

First, go to your browser and load Remix, by going to https://remix.org:

It’s all online, so you don’t have anything to install. Remix is the most popular development environment for Solidity smart contract, and the easiest to get started with Ethereum development. So it’s a good investment to learn how to use it. Beside editing Solidity code, Remix also allows to:

  • Run a local Ethereum blockchain
  • Compile smart contracts
  • Deploy smart contracts on local blockchain
  • Interact with smart contracts with GUI

The fact that Remix manages a local Ethereum Blockchain is a huge win. Without this, you would need to use several separate tools and make them work together. Using Remix massively simplifies smart contract development.

Let’s see what we have in the UI of Remix. The screen is divided in 4 parts: the file editor, the code editor, the console and the toolbox on the right (click to enlarge):

Ethereum Remix IDE tutorial

  • File editor: to create Solidity files (for your smart contracts)
  • Code editor: to edit Solidity code. Has syntax highlighting.
  • Console: to see transaction results (success or error messages).
  • Toolbox: Multiple tabs to compile, deploy (the run tab), debug, smart contracts and more.

2. Code a Solidity smart contract

First, let’s create a file for our smart contract. On the top left corner, in the file editor, click on the “+” icon to create a new file. We will call it “SimpleContract.sol”. Usually, smart contract files start with an uppercase. The extension for Solidity is .sol.

Next, you will see the file appearing in the code editor at the center of the screen. Let’s write a pragma statement:

pragma solidity ^0.5.6;

This should always be written on the first line of your smart contract. This indicate to the compiler which version of Solidity this smart contract was written for. If you try to compile the smart contract with an older version of Solidity, the compiler will refuse to compile. This is a security mechanism. You will also notice that we use a semi-colon to terminate our pragma statement, like for all Solidity statements.

Next, we are going to define our smart contract with this line:

contract SimpleContract {}

In Solidity, to define a smart contract you need to use the contract keyword, followed by the name of the contract. What we have defined is the most simple smart contract possible. Between curly braces you can define variables and functions. In this tutorial, we will keep our smart contract very simple and focus on the workflow instead.

By the way, if you have errors, the syntax highlighting or Remix will show it to you: there will be a red line below it, and a red cross on the left gutter. If you hover on the error, Remix will show you an error message. This is extremely convenient, especially for learning.

3. Compile the smart contract

In the toolbox panel on the right, go to the “Compile” tab. In the dropdown menu, select the same version of Solidity as our pragma statement (i.e Solidity 0.5.6). Next click on the button “Start to compile”. You should see a green box appearing below in case of success. In the next tutorials, make sure to tick “auto-compile” so that you don’t have to manually compile every time you change the source code.

4. Deploy the smart contract on a local blockchain

In the toolbox panel, go to the “Run” tab. This is used to deploy and interact with smart contracts (click to enlarge):

remix ethereum run tab tutorial

For the environment, make sure to choose “Javascript VM”. That means that Remix will deploy your smart contract to a local Ethereum blockchain. This blockchain is not connected to the real Ethereum blockchain (mainnet), and does not use real Ether. You will be given access to 10 Ethereum addresses pre-funded with 100 (fake) Ether each. You can use this Ether like you want: send it to other addresses or use it for running transactions. This is basically a safe sandbox for local development.

The account dropdown show you a list of the 10 pre-funded addresses I just mentioned. You can leave the first one selected. That’s the address that will deploy or interact with the smart contract.

For “gas limit” you can ignore that for the moment. The default value is enough for us.

You can also leave the “value” field to 0. That’s the value of Ether in Wei (10^ -18 Ether) that will be sent to the smart contract for the next transaction.

Below, you will find the deployment panel. That’s where you can deploy smart contract to the Blockchain. You need to first select in the dropdown the smart contract you want (in our case we just have one). Then you click on the “deploy” function.

And finally in the “deployment panel” you will find you deployed smart contract. That’s where you normally have a visual interface to interact with it. In our case, we haven’t defined any function, so there is no interaction possible. Lastly, you can delete a contract instance by clicking on the trash icon next to each contract instance.

That’ it for our first smart contract. Now you understand better the workflow of developing a smart contract with Remix, congrats!

Contract 2: Hello World

For our second smart contract, we will do a simple smart contract HelloWorld smart contract. This contract will be able to return a ‘helloworld’ string. We will learn:

  • how to write a read-only function in Solidity
  • how to specify returns type of a Solidity functions
  • pure and public function modifiers
  • how to call a read-only function from outside the smart contract

Let’s get started!

Go to your Remix editor, and add the pragma statement:

pragma solidity ^0.5.6;

Then, define your smart contract:

contract HelloWorld {}

In Solidity, like with many other programming languages, we define code inside functions. Solidity functions need to be declared inside a contract. Let’s add a function hello():

contract HelloWorld {
    function hello() {}
}

We use the function keyword, followed by the name of the function. Exactly like in Javascript. Inside the parenthesis, we can define function arguments, but for this function we don’t need any.

Between the parenthesis and the curly braces, we can define so-called ‘function modifiers’ and other meta information about the function. This is intended to be used by both the Solidity compiler and for programmers. We will add 2 function modifiers:

  • pure, to indicate that this function does not modify the blockchain.
  • public, to indicate that it can be read from outside the smart contract

And we also need to indicate the return type of the function, with the returns keyword:

returns(string memory)

Because strings are a non-scalar types(i.e a complex type composed of several values. Strings are represented as a dynamic array of bits), we also need to specify the memory location. I don’t want to go too deep on this so early in the tutorial, but basically there are different memory locations, and you need to be explicit for memory locations of non-scalar types like strings). One of these memory locations is just called memory (confusing, I know…), which is a temporary memory location, not stored on blockchain. That’s what we specify here.

In the end, the complete function signature will look like this:

contract HelloWorld {
    function hello() pure public returns(string memory) {}
}

Next, let’s define the function body. Inside the curly braces, we’ll add a return statement:

contract HelloWorld {
    function hello() pure public returns(string) {
        return 'Hello World';
    }
}

This function will return the string ‘Hello World’ to the caller.

Next, let’s go try out our function.

Go to the run tab of Remix, and click on the ‘deploy’ button. It will deploy the HelloWorld smart contract to a local Ethereum blockchain, like we did for the previous smart contract. Once this is done, you should see a rectangle appearing at the bottom of the run tab. This represents the deployed instance of your smart contract. You can use that to interact with your smart contract (i.e call its functions). Click on this rectangle, it will expand a new section. Inside, you will find a green button hello. This allow you to call the hello function of the smart contract. Click on it. You should see Hello World appearing next to it.

Congrats, you just called your first Solidity function!

We just scratched the surface of what is the possible to do in a Solidity function. We can also declare variables of many different types, manipulate them and change the data of the blockchain. We’ll see this in the next smart contract.

Contract 3: Simple storage

This smart contract will read and update a value on the blockchain. We will learn how to:

  • declare a state variable, persisted on the blockchain
  • declare a function that can modify data on the blockchain
  • use string variables
  • memory location

As usual, let’s open up our Remix editor, and create a new file for our smart contract. The first line of the smart contract should be the pragma statement, followed by the name of the contract:

pragma solidity ^0.5.0;

contract SimpleStorage {}

Next, let’s declare a string variable:

pragma solidity ^0.5.0;

contract SimpleStorage {
    string public data;
}

This variable is declared outside of a function. This is what we call a state variable. Contrary to variables declared inside functions, state variables are persisted in the blockchain, even after the execution of a function in the smart contract. You will notice that state variables, like functions, can take visibility modifiers (the public keyword). public variables can be read from outside the smart contract. If you omit the visibility or specify explicitly private, the variable will not be readable from outside the smart contract.

In our case, this a public variable, so it should readable from outside the smart contract. How can we do this? For public variables Solidity creates automatically getter functions of the same name of the variable. So when we write:

string public data;

It’s has if we wrote something like this:

string public _data; //this is an arbitrary name. Solidity mangles the original variable name so that the name is available for our getter function

function data() view public returns(memory string) { //we will explain this weird `memory` keyword later
  return _data;
}

Alright, so next we will create a getter function for the data variable. We don’t strictly need it, because as I just mentioned Solidity will create a getter automatically for public variables. But I just want to be explicit in this smart contract:

pragma solidity ^0.5.0;

contract SimpleStorage {
    string public data;

    function get() view public returns(memory string) {
      return data;
    }
}

A string is actually more complex that it seems, because it has an arbitrary length. For this reason, it cannot be put on the function stack, and need to be in another memory location. In Solidity, they are 4 memory locations:

  • stack
  • memory
  • storage
  • calldata

The stack is the most simple memory location, for fixed-sized data (ex: uint) inside functions. It last only as long as the function. Memory is also short-lived, and is used for variable-length variable data. As a side note, I created this graph of memory locations in Solidity to understand the difference in lifetimes. For temporary strings inside functions, we use the memory location. storage means that the variable is saved inside the blockchain. When you declare a state variable outside any function, it is implied that it has the storage location, so no need to specify it. And for calldata, you can just ignore that for now.

Finally, let’s define a setter function to change the value of the data:

pragma solidity ^0.5.0;

contract SimpleStorage {
  string public data;

  function set(string memory _data) public {
    data = _data;
  }

  function get() view public returns(string memory) {
    return data;
  }
}

Our new set() function was declared without the view keyword. That means that any change to the smart contract storage needs to be persisted in the blockchain. It takes a single string argument that we will assign to the state variable data. You will not that we call this argument almost like the state variable it modifies, but prefixed by an underscore. We often use this pattern when we want to assign an argument to a state variable.

Now, we can both get and set the value of the data string. Pretty cool!

Contract 4: Advanced storage

In this smart contract, like just be fore we will also code a storage contract capable of reading and writing data. But, contrary to last time, this time we will deal with arrays and we will have more functions. We will learn to:

  • Declare arrays
  • Read array elements
  • Create new elements in array

Let’s start by creating an empty smart contract:

pragma solidity ^0.5.0;

contract AdvancedStorage {}

Like in other programming languages, Solidity has arrays to define variable size data structures. Contrary to a Javascript array however, in Solidity you can only put one data type in an array. Let’s declare an array of integers, using the name of the type plus square brackets:

pragma solidity ^0.5.0;

contract AdvancedStorage {
  uint[] public ids;
}

It has a public visibility, which means Solidity will create automatically a getter for it. The getter will return individual elements from the array. It’s as if we had:

pragma solidity ^0.5.0;

contract AdvancedStorage {
  uint[] public _ids;

  function ids(uint _index) external returns(uint) {
    return _ids[_index];
  }
}

How would you define an array of another type, like bytes32 for example? Answer: bytes32[] variableName; Just to make sure you understand the notation.

Let’s move on and add our first function to add elements to our array. In Solidity arrays, we can use the push method to add new elements at the end of it:

pragma solidity ^0.5.0;

contract AdvancedStorage {
  uint[] public ids;

  function add(uint id) public {
    ids.push(id);
  }
}

We also want a getter function to get individual elements from the array. Yes, once again that’s redundant with the getter automatically created by Solidity (ids is public), but let’s be explicit:

  uint[] public ids;

  function add(uint id) public { //No view keyword, this is a function that can modify the blockchain
    ids.push(id);
  }

  function get(uint i) view public returns(uint) { //note the view keyword compared to previous function.
    return ids[i]; //arrays are 0-indexed. i should start at 0
  }
}

Since we have an array, a getter for individual elements is not enough. We also need a getter to get all the elements of the array (getAll()):

pragma solidity ^0.5.0;

contract AdvancedStorage {
  uint[] public ids;

  function add(uint id) public {
    ids.push(id);
  }

  function get(uint i) view public returns(uint) {
    return ids[i];
  }

  function getAll() view public returns(uint[] memory) {
    return ids;
  }
}

And finally, et create a function length() to tell us how many elements the ids array has. Like for Javascript, Solidity arrays have a length attribute:

pragma solidity ^0.5.0;

contract AdvancedStorage {
  uint[] public ids;

  function add(uint id) public {
    ids.push(id);
  }

  function get(uint i) view public returns(uint) {
    return ids[i];
  }

  function getAll() view public returns(uint[] memory) {
    return ids;
  }

  function length() view public returns(uint) {
    return ids.length;
  }
}

Awesome! Now you now the basics of Solidity arrays. This will be very useful for writing your own smart contracts because we very often have to deal with collection of data represented in arrays.

Contract 5: Crud

For our last smart contract, we will step up our game and code a full CRUD smart contract that can manipulate users. CRUD is an abbreviation that means “Create, Read, Update, Delete’, the 4 more common kind of operations on data. Once you know how to do these 4 operations, you covered a lot of use cases and you can re-use these knowledge to build many kind of applications. The same applies to smart contract. So let’s get started!

In this smart contract we will learn:

  • the 4 more common operations with data: create, read, update, delete (crud)
  • how to declare and use struct in Solidity (custom data)
  • how to manage collections of structs in struct arrays

We start with our smart contract definition:

pragma solidity ^0.5.0;

contract Crud {}

For this smart contract, we are going to create a custom data structure, using the struct keyword:

pragma solidity ^0.5.0;

contract Crud {
  struct User {
    uint id;
    string name;
  }
}

Solidity struct are data definition, a bit similar to classes in Javascript, except that you need to specify all the fields in advance. You can’t add a field to a struct once it has been instantiated. In our case we only have to fields,id and name.

Defining a struct is usually not enough. In most cases, you will also need a container to hold all the instances of a struct. It’s possible to define an array of struct, so we are going to do this:

pragma solidity ^0.5.0;

contract Crud {
  struct User {
    uint id;
    string name;
  }
  User[] public users;
  uint public nextId = 1;
}

You will notice that we also define a variable nextId to keep track of what is the next id integer to use for the next instance of User struct to create. We initialize this variable to 1.

Next, let’s start with the create function to create new instances of User:

pragma solidity ^0.5.0;

contract Crud {
  struct User {
    uint id;
    string name;
  }
  User[] public users;
  uint public nextId = 1;

  function create(string memory name) public {
    users.push(User(nextId, name));
    nextId++;
  }
}

This function accepts a single argument, for the name field of User struct. It uses the push method of the users array to add new struct instances. The way we instantiate a struct is by using the name of the struct, followed by parenthesis and the list of fields, separated like arguments. Like if you were calling a function:

User(nextId, name)

We also increment the nextId so that the next time we instantiate a User struct we don’t create a user with same id as the last one:

nextId++;

Next, let’s create a function to read a specific instance of User struct:

pragma solidity ^0.5.0;

contract Crud {
  struct User {
    uint id;
    string name;
  }
  User[] public users;
  uint public nextId = 1;

  function create(string memory name) public {
    users.push(User(nextId, name));
    nextId++;
  }

  function read(uint id) view public returns(uint, string memory) {
    uint j;
    for(uint i = 0; i < users.length; i++) {
      if(users[i].id == id) {
        j = i;
      }
    }
    if(j == 0) {
      revert('User does not exist!');
    }
    return(users[j].id, users[j].name);
  }
}

This function accepts a single id argument, to know which User struct to read. It does not return directly a User struct because Solidity does not know how to do this (unless you use the AbiEncoderV2 experimental pragma statement, but that’s another story). So we need to destructure the User struct and return a tuple of its individual fields. For uint, that’s a fixed-sized type, so we don’t need to specify the memory location, but for string we do.

Inside the read function, we first need to retrieve the index of the User struct we are looking for. In Solidity, we can use for-loop and if statements, exactly like in Javascript:

    uint j;
    for(uint i = 0; i < users.length; i++) {
      if(users[i].id == id) {
        j = i;
      }
    }

Next, if we didn’t find any relevant entry in the users array, the User struct does not exist, and we need to throw an error:

   if(j == 0) {
      revert('User does not exist!');
   }

The revert keyword cancels the current execution, and consume all the gas if we are doing a transaction.

Next, we finally return the content of the struct. For this we reference the correct struct entry using the bracket ([]) notation, then we access each field with the dot (.) notation:

return(users[j].id, users[j].name);

Phew! This function wasn’t easy. But we are learning how to deal with collections of data in Solidity, and that’s going to be very useful.

Next, we will add the update() function to update a User struct:

pragma solidity ^0.5.0;

contract Crud {
  struct User {
    uint id;
    string name;
  }
  User[] public users;
  uint public nextId = 1;

  function create(string memory name) public {
    users.push(User(nextId, name));
    nextId++;
  }

  function read(uint id) view public returns(uint, string memory) {
    uint i = _find(id);
    return(users[i].id, users[i].name);
  }

  function update(uint id, string memory name) public {
    uint i = _find(id);
    users[i].name = name;
  }

  function _find(uint id) view internal returns(uint) {
    for(uint i = 0; i < users.length; i++) {
      if(users[i].id == id) {
        return i;
      }
    }
    revert('User does not exist!');
  }
}

The update function takes 2 arguments: the id of the struct, and the new value for the name field to update. The first thing we do inside the function is to find the index of the struct. That’s exactly the same logic as for the previous function (read()), so I refactored the common logic in the _find() function. This function is internal, which means it can only be called by another function of the smart contract. By the way, in the read() function I have also replaced the logic for for finding the index of the struct by a call to this new _find() function. More DRY (Dont Repeat Yourself). The next thing we do inside the update() function is to update the name field of the User struct we are modifying. We just assign the name argument to the field of the struct, and that’s it!

We are almost there! There is a last function we need to implement: the delete() function, to delete a specific instance of User struct. Let’s do this:

pragma solidity ^0.5.0;

contract Crud {
  struct User {
    uint id;
    string name;
  }
  User[] public users;
  uint public nextId = 1;

  function create(string memory name) public {
    users.push(User(nextId, name));
    nextId++;
  }

  function read(uint id) view public returns(uint, string memory) {
    uint i = _find(id);
    return(users[i].id, users[i].name);
  }

  function update(uint id, string memory name) public {
    uint i = _find(id);
    users[i].name = name;
  }

  function delete(uint id) public {
    uint i = _find(id);
    delete users[i];
  }

  function _find(uint id) view internal returns(uint) {
    for(uint i = 0; i < users.length; i++) {
      if(users[i].id == id) {
        return i;
      }
    }
    revert('User does not exist!');
  }

}

This function is very similar to the update() function. It takes a single argument for the id of the struct to delete. Then it calls _find() to get the index of the struct in the users array. And finally it uses the delete keyword to delete the relevant entry in the users array.

We are finally done with all the smart contracts of this tutorial series! Congrats for following up to here, you are persistent, that’s great 🙂 You have learned the very basics of Solidity smart contracts, and now you can start to write more complex smart contracts.

Where do you go from there? If you want to keep learning Solidity programming and create more advanced smart contracts, follow my 30 days course on Solidity. That’s is the most advanced course on Solidity you can find: we build 30 smart contracts in 30 days, starting with the 5 contracts we did together in this series, and evolving to much more interesting contracts like DAO, Rock paper scissors, Tinder clone, Ebay, ERC20 token, ERC721, and more! In any case, you should at least create a free account on EatTheBlocks so that you can access the source code of all the smart contracts of this series, as well as the code of all the other tutorials of EatTheBlocks.

If you have any questions, send me an email at julien [at] eattheblocks [dot] com

1 Comment

  1. Richard Lippman
    January 31, 2020

    Thanks Great .

Leave a Reply

More great articles

How To Become a Blockchain Developer: Step-by-Step Plan

Do you want to become a become Blockchain developer? With higher salaries, reports of Ethereum Dapps becoming very successful overnight,…

Read Story

How Solana Provides Affordable Daap Scaling

Solana is well positioned providing the infrastructure to support the four pillars of the metaverse: Web 3.0, Decentralized Crypto Trading,…

Read Story

Flash Loans Explained!

Overview Decentralised finance on Ethereum has created a new monetary paradigm. Ethereum’s smart contract architecture has laid the grounds for…

Read Story

Never miss a minute

Get great content to your inbox every week. No spam.
[contact-form-7 id="6" title="Footer CTA Subscribe Form"]
Arrow-up