Solidity Tutorials

Welcome to this smart contract workshop organised by Extropy.

In this tutorial, we are going to build a simple Score Dapp on Ethereum. You are going to learn Solidity, a programming language to create smart contracts on the Ethereum blockchain.

Introduction

First, you would need to open Remix, a browser IDE to write and compile Solidity code.

// Any compiler version from the 0.5 release (= 0.5.x) 
pragma solidity ^0.5.0;    

// Greater than version 0.4.5, less than version 0.5.4
pragma solidity >0.4.5 <0.5.4;

Use the keyword contract followed by your contract name.

contract Score {

    // You will start writing your code here =)

}

Contract would need some state. We are going to declare a new variable that will hold our score.

contract Score {

    uint score = 5;

}

Important !

Solidity is a statically typed language. So you always need to declare the variable type (here uint) before the variable name.

Do not forget to end your declaration statements with a semicolon ;


uint defines an unsigned integer of 256 bits by default.
You can also specify the number of bits, by range of 8 bits. Here are some examples below:

Type Number range
uint8 0 to 255
uint16 0 to 65,535
uint32 0 to 4,294,967,295
uint64 0 to 18,446,744,073,709,551,615
uint128 0 to 2^128
uint256 0 to 2^256

1) Getter and Setter

We need a way to write and retrieve the value of our score. We achieve this by creating a getter and setter functions.

In Solidity, you declare a function with the keyword function followed the function name ( here getScore() ).

contract Score {

    uint score = 5;
    
    function getScore() returns (uint) {
        return score;
    }
    
    function setScore(uint new_score) {
        score = new_score;
    }
}

Let’s look at both functions in detail.


1.1) Getter function using return

Definiton : In Solidity, a getter is a function that returns a value.

To return a value from a function (here our score), you use the following keywords:


1.2) Setter function: pass parameters to our function

Definition : In Solidity, a setter is a function that modifies the value of a variable (modifies the state of the contract). To creates a setter, you must specify the parameters when you declare your function.

After your function name, specifies between parentheses 1) the variable type (uint) and 2) the variable name (new_score)

Compiler Error:

Try entering this code in Remix. We are still not there. The compiler should give you the following error:

Syntax Error: No visibility specified.Did you intend to add "public" ?
Therefore, we need to specify a visibility for our function. We are going to cover the notion of visibility in the next section.

2) Function visibility

2.1) Introduction

To make our functions work, we need to specify their visibility in the contract.

Add the keyword public after your function name.

contract Score {

    uint score = 5;
    
    function getScore() public returns (uint) {
        return score;
    }
    
    function setScore(uint new_score) public {
        score = new_score;
    }
}

There are four types of visibility for functions in Solidity : public, private, external and internal. The table below explains the difference.

Visibility Contract itself Derived Contracts External Contracts External Addresses
public :heavy_check_mark: :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:
private :heavy_check_mark:
internal :heavy_check_mark: :heavy_check_mark:
external :heavy_check_mark: :heavy_check_mark:

Learn More:

uint score public;

Try entering that in Remix. We are still not getting there ! You should receive the following Warning on Remix.

Compiler Warning:

Warning: Function state mutability can be restricted to view.

2.2) View vs Pure ?

Because our function getScore() only reads from the contract state, it is a view function.

function getScore() public view returns (uint) {
    return score;
}

3) Adding Security with Modifiers

Our contract has a security issue: Anyone can modify the score.

Solidity provides a global variable msg, that refers to the address that interacts with the contract’s functions. The msg variables offers two associated fields:

How to restrict a function to a specific caller ?

We should have a feature that enables only certain addresses to change the score (your address). To achieve this, we will introduce the notion of modifiers.

Definition : A modifier is a special function that enables us to change the behaviour of functions in Solidity. It is mostly used to automatically check a condition before executing a function.

We will use the following modifier to restrict the function to only the contract owner.

address owner;
    
modifier onlyOwner {
    if (msg.sender == owner) {
       _;
    }
}

function setScore(uint new_score) public onlyOwner {
    score = new_score;
}

The modifier works with the following flow:

  1. Check that the address of the caller (msg.sender) is equal to owner address.

  2. If 1) is true, it passes the check. The _; will be replaced by the function body where the modifier is attached.

A modifier can receive arguments like functions. Here is an example of a modifier that requires the caller to send a specific amount of Ether.

modifier Fee(uint fee) {
    if (msg.value == fee) {
        _;
    }
}

However, we still haven’t defined who the owner is. We will define that in the constructor.

4) Constructor

Definition : A constructor is a function that is executed only once when the contract is deployed on the Ethereum blockchain.

In the code below, we define the contract owner:

contract Score {

    address owner;

    constructor() public {
        owner = msg.sender;
    }

}

Learn More:

Constructors are optional. If there is no constructor, the contract will assume the default constructor, which is equivalent to constructor () public {}

Warning !

Prior to version 0.4.22, constructors were defined as function with the same name as the contract. This syntax was deprecated and is not allowed in version 0.5.0.

5) Events

Events are only used in Web3 to output some return values. They are a way to show the changes made into a contract.

Events act like a log statement. You declare Events in Soldity as follow:

// Outside a function
event myEvent(type1, type2, ... );

// Inside a function
emit myEvent(param1, param2, ... );

To illustrate, we are going to create an event to display the new score set. This event will be passed within our setScore() function. Remember that you should pass the score after you have set the new variable.

event Score_set(uint);

function setScore(uint new_score) public onlyOwner {
    score = new_score;
    emit Score_set(new_score);
}

You can also use the keyword indexed in front of the parameter’s types in the event definition.

It will create an index that will enable to search for eventsvia Web3 in your front-end.

event Score_set(uint indexed);

Note !

event can be used with any functions types (public, private, internal or external). However, they are only visible outside the contract.

event are are not visible internally by Solidity. You cannot read an event. So a function cannot read the event emitted by another function for instance.

Learn more !

When you call an event in Solidity, the arguments passed to it are stored in the transaction’s log - a special data structure in the Ethereum blockchain. These logs are associated with the address of the contract, incorporated into the blockchain, and stay there as long as a block is accessible

6) References Data Types: Mappings

Mappings are another important complex data type used in Solidity. They are useful for association, such as associating an address with a balance or a score. You define a mapping in Solidity as follow:

mapping(KeyType => ValueType) mapping_name;

You can find below a summary of all the datatypes supported for the key and the value in a mapping.

type Key Value
int/uint :heavy_check_mark: :heavy_check_mark:
string :heavy_check_mark: :heavy_check_mark:
bytes :heavy_check_mark: :heavy_check_mark:
address :heavy_check_mark: :heavy_check_mark:
struct :x: :heavy_check_mark:
mapping :x: :heavy_check_mark:
enums :x: :heavy_check_mark:
Contract :x: :heavy_check_mark:
fixed-sized array :heavy_check_mark: :heavy_check_mark:
dynamic-size array :x: :heavy_check_mark:
variable :x: :x:

You can access the value associated with a key in a mapping by specifing the key name inside square brackets [] as follows: mapping_name[key].

Our smart contract will store a mapping of all the user’s addresses and their associated score. The function getUserScore(address _user) enables to retrieve the score associated to a specific user’s address.

mapping(address => uint) score_list;

function getUserScore(address user) public view returns (uint) {
    return score_list[user];
}

Tips:

you can use the keyword public in front of a mapping name to create automatically a getter function in Solidity, as follow:

mapping(address => uint) public score_list;

Learn More:

In Solidity, mappings do not have a length, and there is no concept of a value associated with a key.

Mappings are virtually initialized in Solidity, such that every possible key exists and is mapped to a value which is the default value for that datatype.


7) Reference Data Types: Arrays

Arrays are also an important part of Solidity. You have two types of arrays (T represents the data type and k the maximum number of elements):

uint[] all_possible_number;        
uint[9] one_digit_number;

In Solidity, arrays are ordered numerically. Array indices are zero based. So the index of the 1st element will be 0. You access an element in an array in the same way than you do for a mapping:

uint my_score = score_list[owner];

You can also used the following two methods to work with arrays:

8) Structs

We can build our own datatypes by combining simpler datatypes together into more complex types using structs
We use the keyword struct, followed by the structure name , then the fields that make up the structure.
For example

  struct Funder {
        address addr;
        uint amount;
    }

Here we have created a datatype called Funder, that is composed of an address and a uint.

We can now declare a variable of that type

Funder giver;

and reference the elements using dot notation

giver.addr = address (0xBA7283457B0A138890F21DE2ebCF1AfB9186A2EF);
giver.amount = 2500;

The size of the structure has to be finite, this imposes restrictions on the elements that can be included in the struct.

Example of a contract using reference datatypes

Click to expand!
pragma solidity ^0.5.0;

contract ListExample {

  struct DataStruct {
    address userAddress;
    uint userID;
  }

  DataStruct[] public records;

  function createRecord1(address _userAddress, uint _userID) public returns(uint recordNumber) {
    DataStruct memory newRecord;
    newRecord.userAddress = _userAddress;
    newRecord.userID    = _userID;
    return records.push(newRecord)-1;
  }

  function createRecord2(address _userAddress, uint _userID) public returns(uint recordNumber) {
    return records.push(DataStruct({userAddress:_userAddress,userID:_userID}))-1;
  }

  function getRecordCount() public view returns(uint recordCount) {
    return records.length;
  }
}

Official Solidity Documentation

Globally Available Variables

Open Zeppelin Libraries

Online Help

WTF is Ethereum
Ethereum Stack Exchange
Ethereum Magicians
Ethereum Research

Other Tutorials

Crypto Zombies