⚡Solidity

Here's a comprehensive course on Solidity, the programming language used to develop smart contracts on the Ethereum blockchain.

Hello world

Overall structure of a smart contract : A Solidity smart contract generally follows the following structure:

// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.0 and less than 0.9.0
pragma solidity ^0.8.0;

// Contract declaration
contract MyContract {
    // Events
    
    // State variables
    
    // Modifiers
    
    // Constructor
    
    // Functions
}

📌 Explanation of keywords:

  • msg.sender : Address of the sender of the message calling the function.

  • block.timestamp : Timestamp of the block in seconds since the Unix era.

  • address(this) : Address of the current contract.

  • address(this).balance : Balance of the current contract in Ether.

  • Ether and Wei : Ether is the basic unit used to measure the value of the cryptocurrency in Ethereum. Wei is the smallest unit, where 1 Ether equals 10^18 Wei.

  • Gas and Gas Price: Gas is a unit of measurement for the amount of work involved in executing transactions and contracts on Ethereum. The gas price is the price per unit of gas paid to execute a transaction or contract. The total cost of a transaction is calculated by multiplying the gas used by the gas price.


💡 Here is an example of a simple "Hello World" contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    string public message;

    constructor() {
        message = "Hello, World!";
    }

    function setMessage(string memory _newMessage) public {
        message = _newMessage;
    }
}

This "Hello World" contract has a message state variable which stores a text message. The constructor initializes the variable with Hello, World! The setMessage function is used to modify the message.

Now you have a basic understanding of the structure of a smart contract and the keywords commonly used in Solidity. Explore and experiment further to create more complex contracts!

Introduction to Solidity and smart contracts

In this first chapter, we'll learn about Solidity, the programming language used to develop smart contracts on the Ethereum blockchain. We will also explore what a smart contract is and why Solidity is used in this context.

🤔 What is Solidity?

Solidity is an object-oriented programming language specially designed for writing smart contracts on the Ethereum blockchain. It is used to define the rules and behaviour of smart contracts, which are autonomous programs that run on the blockchain.


💡 What is a smart contract?

A smart contract is a self-executing computer program that runs on the Ethereum blockchain. It acts as a digital agreement that defines the terms and conditions of a transaction between two parties. Smart contracts enable processes to be automated and transactions to be executed transparently and securely without the need for an intermediary.


🔑 Why use Solidity?

Solidity is the most widely used programming language for developing smart contracts on Ethereum. Here are a few reasons why Solidity is popular:

  1. Ethereum's compatibility: Solidity is specifically designed to work with Ethereum, the most widely used blockchain platform for smart contracts.

  2. Object-oriented programming support: Solidity supports object-oriented programming concepts such as inheritance and complex data structures, making it easy to create modular and reusable smart contracts.

  3. Security: Solidity includes security features such as exception handling and assertions, which help minimize vulnerabilities and ensure the robustness of smart contracts.

  4. Large community and resources: Solidity has an active developer community and many online resources, making it easy to learn and share knowledge.


Now that you understand the importance of Solidity and smart contracts, you're ready to dive deeper into this programming language. In the next chapter, we'll look at data types and variables in Solidity.

Data types and variables
  1. Basic data types

  • bool: represents true or false Boolean values.

  • uint: unsigned integer, for example uint256 for a 256-bit integer.

  • int: signed integer, e.g. int8 for an 8-bit integer.

  • address: Ethereum address to which to send Ether or call functions.

  • bytes: array of binary data of variable size.

  • string: character string encoded in UTF-8.


  1. Variable declaration :

To declare a variable, use the following syntax:

type visibility variableName;

Visibility can be specified by the following keywords:

  • public: the variable can be read from outside the contract.

  • private: the variable can only be accessed from inside the contract.

  • internal: the variable can be accessed from within the contract and from inherited contracts.

  • external: the variable can only be used by functions external to the contract.

Here's an example:

uint256 public myNumber;
address private myAddress;

  1. Arrays and data structures

  • Arrays: store several elements of the same type. There are dynamic arrays (modifiable size) and static arrays (fixed size). Here's an example:

uint256[] public myArray ; // Dynamic array of uint256
uint256[5] public myFixedArray; // Static array of uint256 with a fixed size of 5

The arrays have several methods as follows:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract Array {
    uint[] public arr;

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

    // Return the entire array.
    function getArr() public view returns (uint[] memory) {
        return arr;
    }

    function push(uint i) public {
        // Append to array
        arr.push(i);
    }

    function pop() public {
        // Remove last element from array
        arr.pop();
    }

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

    function remove(uint index) public {
        delete arr[index];
    }
}

  • Structures: define custom data types that can contain several fields of different types. Example:

struct Person {
    string name;
    uint256 age;
}
Person public myPerson;

  1. Mapping:

Mapping is a key-value data structure widely used in Solidity. It is used to associate a key with a value, similar to a dictionary or hash table.

// Declaring a mapping
mapping(address => uint256) public balances;

// Assign a value to a key
balances[msg.sender] = 100;

// Access the value associated with a key
uint256 myBalance = balances[msg.sender];

In this example, we use a mapping to store the balances of addresses. Each address is associated with an amount (uint256). We can access the value associated with an address by using the address as a key.


  1. Double Mapping :

Double mapping is an extension of mapping where a second key is added to create a two-dimensional data structure. This allows two keys to be associated with a value.

// Declaring a double mapping
mapping(address => mapping(uint256 => bool)) public approvals;

// Assign a value to a pair of keys
approvals[msg.sender][tokenId] = true;

// Access the value associated with a key pair
bool isApproved = approvals[msg.sender][tokenId];

In this example, we use a double mapping to store approvals for addresses (address) for tokens (tokenId). Each pair of address and token is associated with a boolean that indicates whether the approval is granted or not.

Mappings and double mappings are powerful data structures in Solidity, often used for data storage and management in smart contracts. Feel free to use them in your own projects!


  1. Type operators and conversions :

  • Common mathematical operators are supported, such as +, -, *, / for integers and decimals.

  • Type conversions can be performed explicitly using the cast function, or implicitly in some cases.


  1. Constants and enums :

  • Constants can be defined using the constant or immutable keyword.

uint256 constant public MY_CONSTANT = 123;
address public immutable MY_ADDRESS;
  • Enumerations (enum) are used to define a set of constant values.

enum State {Inactive, Active, Paused}
State public currentState;

This was an introduction to data types and variables in Solidity. In the next lesson, we'll explore functions and modifiers.

Data locations

In Solidity, there are three main data locations: Storage, Memory and Calldata.

1. Storage:

Storage is the permanent location of data on the Ethereum blockchain. Variables declared outside of functions are stored in storage by default. To access data in storage, use the storage keyword.

Here is an example:

contract MyContract {
  uint256 myInteger; // Variable stored in storage

  function setEntier(uint256 _value) public {
    myInteger = _value; // Write to storage
  }

  function getEntier() public view returns (uint256) {
    return myInteger; // Read from storage
  }
}

  1. Memory :

Memory is used to store temporary variables inside functions. Variables declared inside functions are automatically allocated in memory. To access data in memory, use the memory keyword.

Here is an example:

contract MyContract {
  function concatener(string memory _a, string memory _b) public pure returns (string memory) {
    string memory resultat = string(abi.encodePacked(_a, _b)); // Concatenation in memory
    return resultat;
  }
}

  1. Calldata :

Calldata is used to store arguments passed when calling a function from another function or contract. Function parameters marked with the calldata keyword are read-only. They are useful for saving memory when calling external functions.

Here's an example:

contract MyContract {
  function verifierDonnees(bytes calldata _donnees) public pure returns (uint256) {
    return _donnees.length; // Read data from calldata
  }
}

This summarizes the essential data locations in Solidity: Storage, Memory and Calldata. Make sure you understand these concepts to manage data well in your smart contracts!

Functions and modifiers

In Solidity, functions are the reusable blocks of code that perform specific actions in a smart contract. Modifiers, on the other hand, are directives that modify the behaviour of functions. Let's see this in detail!

  1. Definition of functions :

Functions are defined within a contract and can have arguments and return values. Here is the general syntax for declaring a function:

function functionName(type argument1, type argument2) visibility [modifiers] returns (type) {
    // Function body
    // Instructions to execute
    return returnvalue;
}
  • functionName` is the name of the function.

  • type` is the data type of the arguments and the return value.

  • visibility` specifies who can call the function (public, private, internal or external).

  • [modifiers] are optional directives for modifying the behaviour of the function.

  • returns (type) specifies the type of the return value.


  1. Arguments and return values :

Functions can take arguments as input and return values as output. Here is an example of a function that takes two integers as input and returns their sum:

function add(uint a, uint b) public returns (uint) {
    uint sum = a + b ;
    return sum ;
}

  1. Function modifiers :

Modifiers are used to modify the behaviour of a function. They can be used to define preconditions or modify the effects of the function. Here's how to define a modifier:

modifier modifierName(type argument1, type argument2) {
    // Checks or modifications
    _; // Indicates where the body of the function will be executed
}

Here is an example of using a modifier to check whether the caller is the owner of the contract:

modifier onlyOwner() {
    require(msg.sender == owner, "You are not authorised.");
    _;
}

function changeOwner(address newOwner) public onlyOwner {
    owner = newOwner;
}

In this example, the changeOwner function will only be executed if the caller is the owner of the contract.

💡 In addition to modifiers, there are two particular types of function:

🔸 "View" functions: These functions can read the data in the contract state, but cannot modify it

function getTotal() public view returns (uint) {
    return total ;
}

🔸 "Pure" functions: These functions cannot read the data in the contract state, nor can they modify it.

function add(uint a, uint b) public pure returns (uint) {
    returns a + b ;
}

These function types allow the behaviour and use of functions to be specified according to their specific needs.

💡 Modifiers improve code readability, reduce redundancy and enhance the security of smart contracts.

That's it for the section on functions and modifiers in Solidity! You can now create reusable functions and modify their behaviour using modifiers. Let's move on to the next section: Error and exception handling.

Handling errors and exceptions

When developing smart contracts in Solidity, it's important to handle errors and exceptions appropriately. This ensures the robustness and security of your contract. Here are the main concepts to be aware of:

  1. Using assertions 🛡ī¸

Assertions are powerful tools for checking specific conditions in your code. They can be used to ensure that certain conditions are met at key points in the execution of the contract. Assertions are generally used to validate the inputs or results of a function. For example :

function div(uint256 a, uint256 b) public pure returns (uint256) {
  require(b != 0, "Division by zero !") ;
  return a / b ;
}

  1. Throwing custom exceptions ❌❗❗

When you want to signal an error or unwanted behaviour in your contract, you can throw customized exceptions. This interrupts the execution of the contract and rejects the transaction in progress. Here's how to throw an exception:

function withdraw(uint256 amount) public {
  require(amount <= balances[msg.sender], "Insufficient balance!");
  // Make the withdrawal
  balances[msg.sender] -= amount;
  // Send the funds
  if (!msg.sender.send(amount)) {
    revert("Funds transfer failed!");
  }
}

  1. Handling exceptions with try-catch 🔁❓

Solidity also offers a try-catch mechanism to handle exceptions and errors more robustly. You can surround part of your code with a try statement, then catch and handle exceptions using a catch statement. Here's an example:

function safeTransfer(address to, uint256 amount) public {
  try this.transfer(to, amount) {
    // Success: perform additional actions if necessary
  } catch Error(string memory errorMessage) {
    // Handle the error using the error message
  } catch (bytes memory errorData) {
    // Handle the error using the raw error data
  }
}

By handling errors and exceptions appropriately, you can make your contracts more robust and prevent potential security issues. Make sure you use assertions to validate conditions, throw custom exceptions to report errors, and use the try-catch mechanism to handle exceptions in a more controlled way.

Let's continue with the next chapter: Inheritance and libraries.

Heritage and Libraries

When developing smart contracts in Solidity, it's essential to understand inheritance and libraries. These are key concepts that allow you to reuse code and make your code more modular and maintainable.

  1. Inheriting contracts

Inheritance in Solidity allows you to create new contracts using existing contracts as a base. This allows you to extend the functionality of the existing contract while reusing its code.

To inherit from a contract, you use the keyword "is" followed by the name of the parent contract. Here's an example:

contract ParentContract {
    // Parent contract code
}
contract ChildContract is ParentContract {
    // Child contract code
}

In this example, the ChildContract contract inherits from the ParentContract contract. This means that ChildContract will inherit all variables, functions and modifiers defined in ParentContract.


  1. Using libraries

Libraries are created using the library keyword. They can contain functions, data structures and constants.

Here's an example of a simple library that provides a function to calculate the square of an integer:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

library MathLibrary {
    function square(uint256 x) public pure returns (uint256) {
        return x * x;
    }
}

In this example, we've defined a MathLibrary which contains a square function that takes an x parameter of type uint256 and returns the square of x as its return value. The function is declared public and pure, which means that it does not modify the state of the contract and has no access to stored values. It can be called from other contracts.

To use this library in a contract, we need to import the file where it is defined using the import statement. Then we can call the square function as follows:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./MathLibrary.sol";

contract MyContract {
    function calculateSquare(uint256 x) public pure returns (uint256) {
        return MathLibrary.square(x);
    }
}

In this example, we import the MathLibrary.sol library and define a MyContract that uses the calculateSquare function. This function calls the library's square function to calculate the square of x and returns it as the return value.

🎓 Congratulations! You now have a good understanding of inheritance and libraries in Solidity. Keep practising and exploring these concepts to improve your smart contract development skills!

Interfaces and abstract contracts

In Solidity, interfaces and abstract contracts are powerful tools for defining generic contracts and interactions with other contracts without implementing specific functionality.

  1. Interfaces

An interface in Solidity is a declaration of functions without implementation. It defines a set of functions that any contract implementing this interface must respect. Interfaces are used to define generic contracts or to interact with external contracts. Here's how to define an interface:

interface MyInterface {
    function myFunction() external;
    // Other interface functions
}

Contracts wishing to implement this interface must define all the interface functions with the same specifications. For example

contract MyContract is MyInterface {
    function myFunction() external {
        // Implementation of the interface function
    }
}

  1. Abstract contracts

Abstract contracts in Solidity are incomplete contracts that serve as templates for other contracts. They can contain abstract functions, which are function declarations without implementation. Abstract contracts cannot be deployed on their own, but they can be used as parent contracts to provide common structure and functionality.

Here's how to define an abstract contract with an abstract function:

abstract contract MyAbstractContract {
    function myAbstractFunction() external virtual;
    // Other abstract or implemented functions
}

Contracts that inherit from an abstract contract must implement all the abstract functions of the parent contract. For example :

contract MyConcreteContract is MyAbstractContract {
    function myAbstractFunction() external override {
        // implementation of the abstract function
    }
}

🎓 Congratulations! You now have a good understanding of interfaces and abstract contracts in Solidity. Use them to create generic contracts and facilitate interactions with other contracts! Keep exploring and practising, deepening your knowledge of smart contract development.

Events and Logs
  1. Introduction to events:

Events are used to notify interested parties when a specific event occurs in the contract. They are used to communicating important information transparently. ⚡ī¸

Event definition: To define an event, use the keyword event followed by the event name and associated parameters. Events can have parameters that contain specific data to be logged.

event Transfer(address indexed from, address indexed to, uint256 value);

Emitting an event:To emit an event, use the emit keyword followed by the name of the event and the values of the corresponding parameters.This informs observers that the event has occurred. To emit an event, use the "emit" keyword followed by the name of the event and the values of the corresponding parameters.

emit Transfer(msg.sender, recipient, amount);

  1. Using logs:

Logs are similar to events, but they cannot be listened to. They are mainly used to record information in the blockchain, which can be consulted later for verifications or audits.

log("New request created: ", requestId);

Writing to logs:

To write to logs, use the keyword "log" followed by the information you wish to record. You can record structured data for later analysis.

log("The user's balance is: ", balance);

Congratulations! You now have a concise introduction to Events and Logs in Solidity. Feel free to explore these concepts further by practising with more complex code examples. Ready to move on to the next chapter? Let's get started

Control structures

Control structures allow you to control the execution of code according to specific conditions. Here are the main control structures used in Solidity:

  1. Loops:

🔁 The "for" loop lets you repeat a series of instructions a certain number of times. It is useful when you know the number of iterations in advance.

⚠ī¸ Be sure to avoid infinite loops, as they can consume a lot of gas on the Ethereum blockchain.

for (uint i = 0; i < 10; i++) {
    // Instructions to be executed at each iteration
}

🔄 The "while" loop lets you repeat a series of instructions as long as a condition is true. It is used when you don't know the number of iterations in advance.

uint i = 0;
while (i < 10) {
    // Instructions to be executed as long as the condition is true
    i++;
}

  1. Decision structures :

✅ The "if" statement allows you to execute a block of code if a condition is true.

if (condition) {    
// Instructions to be executed if the condition is true
}

❌ The "else" statement allows you to execute an alternative block of code if the condition in the "if" statement is false.

if (condition) {    
// Instructions to be executed if the condition is true
} else {
    // Instructions to be executed if the condition is false
}

Now that you know the control structures in Solidity, you can control the flow of execution of your code according to the conditions required. Keep practising and exploring more of this powerful language!

Interaction with other contracts

When developing smart contracts in Solidity, it is often necessary to interact with other contracts already deployed on the Ethereum blockchain.

📞 External function calls:

To call an external function from another contract, you need to know the address of the contract and its interface. The interface specifies the functions you can call and the associated data types. Here's how to make an external function call:

  1. Define the external contract interface:

interface ExternalContract {
    function someFunction(uint256 param) external returns (uint256);
}

  1. Declare a variable from the external contract using the interface:

ExternalContract externalContract = ExternalContract(contractAddress);

  1. Call the external function:

uint256 result = externalContract.someFunction(param);

👍 Congratulations! You now have the basics to interact with other contracts using Solidity.

Send and receive Ether

Solidity lets you send and receive Ether (Ethereum's native cryptocurrency) from your contract. You have several options for carrying out these operations:

  1. Sending Ether using transfer :

The transfer method is the easiest way to send Ether to a specific address, but it has a fixed gas limit and returns a boolean indicating whether the operation succeeded or failed. Here's how to use it:

address payable recipient = 0x...; // Address of recipient
recipient.transfer(amount);

  1. Sending Ether with send :

The send method is similar to transfer, but it does not raise an exception on failure and simply returns false. It is recommended to use transfer instead of send for security reasons. Here's how to use it:

address payable recipient = 0x...; // Address of recipient
bool success = recipient.send(amount);

  1. Sending Ether with call :

The call method is used to send Ether and call functions from an external contract. It returns true if the call is successful and false otherwise. Here's how to use it:

address payable recipient = 0x...; // Address of recipient
bool success = recipient.call{value: amount}("");

payable functions and payable addresses :

To allow your contract to receive Ether, you must mark your functions with the payable keyword. This indicates that the function can receive Ether when it is called. For example:

function myFunction() public payable {
    // Code executed when a transaction sends Ether to this function
}

To enable your contract to receive Ether when it is deployed, you can add a payable builder. This allows you to specify an amount of Ether that will be sent to the contract when it is created. For example :

contract MyContract {
    constructor() payable {
        // Code executed during deployment with Ether sent
    }
}

In addition, to store Ether in a variable or to declare an address capable of receiving Ether, you must use the payable type. For example:

address payable myAddress = payable(0x...); // Address able to receive Ether
uint256 myValue = 100 ether; // Store 100 Ether in the myValue variable

👍 Perfect! You now have an understanding of the transfer, send and call methods for sending Ether, as well as a brief explanation of the payable functions, payable addresses and the payable constructor. Keep exploring and experimenting with the possibilities offered by Solidity and the Ethereum blockchain!

Receive() and Fallback()

Here's a simple explanation of the receive() and fallback() functions in Solidity, accompanied by little snippets of code for you my bro. 🤖đŸ’ģ

  1. The receive() function

The receive() function is a special function available in Solidity contracts since version 0.6.0. It is used to receive ethers sent directly to the contract. Here is an example of its use:

contract MyContract {
    // receive() payable function
    receive() external payable {
        // Processing to be performed when an ether is sent to the contract
    }
}

In this example, the receive() function is defined with the "payable" modifier, which means that the contract can receive ethers. When an ether is sent to the contract, the receive() function will be automatically called and any code inside will be executed. This function is often used to accept payments.


  1. Fallback() function

The fallback() function is another special function available in Solidity contracts. Prior to version 0.6.0, this function was used to receive ethers. Since Solidity 0.6.0, the fallback() function has been split into two functions: fallback() and receive().

The fallback() function is used to capture invalid or unspecified function calls. Here's an example:

contract MyContract {
    // fallback() function
    fallback() external {
        // Processing to be performed when an invalid function call is made
    }
}

In this example, the fallback() function is defined without the "payable" modifier, which means that it cannot receive ethers. It is used to handle invalid function calls and perform specific processing.

The receive() and fallback() functions are special functions in Solidity that are used to receive ethers and handle invalid function calls. The receive() function is used to receive ethers, while the fallback() function is used to capture invalid function calls.

By understanding and using these functions, you can create more flexible smart contracts and manage interactions with ethers more efficiently.

💡 Conclusion 💡

That concludes our Solidity course! I hope it has helped you better understand this essential programming language for developing smart contracts on the Ethereum blockchain. My bro, feel free to continue exploring and deepening your knowledge in this exciting field! ✨🚀

Last updated