Contrats

Les contrats en Solidity sont similaires à des classes dans les langages orientés objets. Ils contiennent des données persistentes dans des variables et des fonctions peuvent les modifier. Appeler la fonction d’un autre contrat (une autre instance) executera un appel de fonction auprès de l’EVM et changera alors le contexte, rendant inaccessibles ces variables. A contract and its functions need to be called for anything to happen. There is no « cron » concept in Ethereum to call a function at a particular event automatically.

Créer des contrats

Les contrats peuvent être créés « en dehors » via des transactions Ethereum ou depuis des contrat en Solidity.

Les EDIs, comme Remix, facilite la tâche via des éléments visuels.

Créer des contrats via du code se fait le plus simplement en utilisant l’API Javascript web3.js. Elle possède une fonction appelée web3.eth.Contract qui facilite cette création

Quand un contrat est créé, son constructeur (une fonction déclarée via le mot-clé constructor) est executé, de manière unique.

Un constructeur est optionnel. Aussi, un seul constructeur est autorisé, ce qui signifie que l’overloading n’est pas supporté.

Après que le constructeur ai été exécuté, le code final du contrat est déployé sur la Blockchain. Ce code inclut toutes les fonctions publiques et externes, et toutes les fonctions qui sont atteignables par des appels de fonctions. Le code déployé n’inclut pas le constructeur ou les fonctions internes uniquement appelées depuis le constructeur.

En interne, les arguments du constructeur sont passés ABI encodés après le code du contrat lui-même, mais vous n’avez pas à vous en soucier si vous utilisez web3.js.

Si un contrat veut créer un autre contrat, le code source (et le binaire) du contrat créé doit être connu du créateur. Cela signifie que les dépendances cycliques de création sont impossibles.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;


contract OwnedToken {
    // TokenCreator est un type de contrat défini ci-dessous.
    // Il est possible de le référencer tant qu'il n'est pas utilisé
    // pour créer un nouveau contrat.
    TokenCreator creator;
    address owner;
    bytes32 name;

    // This is the constructor which registers the
    // creator and the assigned name.
    constructor(bytes32 _name) {
        // State variables are accessed via their name
        // and not via e.g. `this.owner`. Functions can
        // be accessed directly or through `this.f`,
        // but the latter provides an external view
        // to the function. Especially in the constructor,
        // you should not access functions externally,
        // because the function does not exist yet.
        // See the next section for details.
        owner = msg.sender;
        // Nous effectuons une conversion de type explicite de `address`.
        // vers `TokenCreator` et supposons que le type du
        // contrat appelant est TokenCreator,
        // Il n'y a pas vraiment moyen de vérifier ça.
        creator = TokenCreator(msg.sender);
        name = _name;
    }

    function changeName(bytes32 newName) public {
        // Seul le créateur peut modifier le nom --
        // la comparaison est possible puisque les contrats
        // sont explicitement convertibles en adresses.
        if (msg.sender == address(creator))
            name = newName;
    }

    function transfer(address newOwner) public {
        // Seul le propriétaire actuel peut transférer le token.
        if (msg.sender != owner) return;

        // Nous voulons aussi demander au créateur si le transfert
        // est valide. Notez que ceci appelle une fonction de la fonction
        // contrat défini ci-dessous. Si l'appel échoue (p. ex.
        // en raison d'un manque de gas), l'exécution échoue également ici.
        if (creator.isTokenTransferOK(owner, newOwner))
            owner = newOwner;
    }
}

contract TokenCreator {
    function createToken(bytes32 name)
       public
       returns (OwnedToken tokenAddress)
    {
        // Créer un nouveau contrat Token et renvoyer son adresse.
        // Du côté JavaScript, le type de retour est simplement
        // `address`, car c'est le type le plus proche disponible dans
        // l'ABI.
        return new OwnedToken(name);
    }

    function changeName(OwnedToken tokenAddress, bytes32 name) public {
        // Encore une fois, le type externe de `tokenAddress' est
        // simplement `adresse`.
        tokenAddress.changeName(name);
    }

    function isTokenTransferOK(address currentOwner, address newOwner)
        public
        pure
        returns (bool ok)
    {
        // Vérifier une condition arbitraire.
        return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f;
    }
}

Visibilité et Getters

State Variable Visibility

public

Public state variables differ from internal ones only in that the compiler automatically generates getter functions for them, which allows other contracts to read their values. When used within the same contract, the external access (e.g. this.x) invokes the getter while internal access (e.g. x) gets the variable value directly from storage. Setter functions are not generated so other contracts cannot directly modify their values.

internal

Internal state variables can only be accessed from within the contract they are defined in and in derived contracts. They cannot be accessed externally. This is the default visibility level for state variables.

private

Private state variables are like internal ones but they are not visible in derived contracts.

Avertissement

Making something private or internal only prevents other contracts from reading or modifying the information, but it will still be visible to the whole world outside of the blockchain.

Function Visibility

Puisque Solidity connaît deux types d’appels de fonction (internes qui ne créent pas d’appel EVM réel (également appelés a « message call ») et externes qui le font), il existe quatre types de visibilités pour les fonctions et les variables d’état.

Les fonctions doivent être spécifiées comme étant external, public, internal ou private. Pour les variables d’état, external n’est pas possible.

external:

Les fonctions externes font partie de l’interface du contrat, ce qui signifie qu’elles peuvent être appelées à partir d’autres contrats et via des transactions. Une fonction externe f ne peut pas être appelée en interne (c’est-à-dire f()``ne fonctionne pas, mais ``this.f() fonctionne). Les fonctions externes sont parfois plus efficaces lorsqu’elles reçoivent de grandes quantités de données.

public:

Les fonctions publiques font partie de l’interface du contrat et peuvent être appelées en interne ou via des messages. Pour les variables d’état publiques, une fonction getter automatique (voir ci-dessous) est générée.

internal:

Ces fonctions et variables d’état ne sont accessibles qu’en interne (c’est-à-dire à partir du contrat en cours ou des contrats qui en découlent), sans utiliser this.

private:

Les fonctions privées et les variables d’état ne sont visibles que pour le contrat dans lequel elles sont définies et non dans les contrats dérivés.

Note

Tout ce qui se trouve à l’intérieur d’un contrat est visible pour tous les observateurs extérieurs à la blockchain. Passer quelque chose en private

ne fait qu’empêcher les autres contrats d’accéder à l’information et de la modifier, mais elle sera toujours visible pour le monde entier à l’extérieur de la blockchain.

Le spécificateur de visibilité est donné après le type pour les variables d’état et entre la liste des paramètres et la liste des paramètres de retour pour les fonctions.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract C {
    function f(uint a) private pure returns (uint b) { return a + 1; }
    function setData(uint a) internal { data = a; }
    uint public data;
}

Dans l’exemple suivant, D, peut appeler c.getData() pour retrouver la valeur de data en mémoire d’état, mais ne peut pas appeler f. Le contrat E est dérivé du contrat C et peut donc appeler compute.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract C {
    uint private data;

    function f(uint a) private pure returns(uint b) { return a + 1; }
    function setData(uint a) public { data = a; }
    function getData() public view returns(uint) { return data; }
    function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}

// Ceci ne compile pas
contract D {
    function readData() public {
        C c = new C();
        uint local = c.f(7); // Erreur: le membre `f` n'est pas visible
        c.setData(3);
        local = c.getData();
        local = c.compute(3, 5); // Erreur: le membre `compute` n'est pas visible
    }
}

contract E is C {
    function g() public {
        C c = new C();
        uint val = compute(3, 5); // accès à un membre interne (du contrat dérivé au contrat parent)
    }
}

Fonctions Getter

Le compilateur crée automatiquement des fonctions getter pour toutes les variables d’état public. Pour le contrat donné ci-dessous, le compilateur va générer une fonction appelée data qui ne prend aucun argument et retourne un uint, la valeur de la variable d’état data. Les variables d’état peuvent être initialisées lorsqu’elles sont déclarées.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract C {
    uint public data = 42;
}

contract Caller {
    C c = new C();
    function f() public view returns (uint) {
        return c.data();
    }
}

Les fonctions getter ont une visibilité externe. Si le symbole est accédé en interne (c’est-à-dire sans this.), il est évalué à une variable d’état. S’il est accédé de l’extérieur (c’est-à-dire avec this.), il évalue à une fonction.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;

contract C {
    uint public data;
    function x() public returns (uint) {
        data = 3; // accès interne
        return this.data(); // accès externe
    }
}

Si vous avez une variable d’état public de type array, alors vous ne pouvez récupérer que des éléments simples de l’array via la fonction getter générée. Ce mécanisme permet d’éviter des coûts de gas élevés lors du retour d’un tableau complet. Vous pouvez utiliser des arguments pour spécifier quel élément individuel retourner, par exemple data(0). Si vous voulez retourner un tableau entier en un appel, alors vous devez écrire une fonction, par exemple :

  // SPDX-License-Identifier: GPL-3.0
  pragma solidity >=0.4.16 <0.9.0;

contract arrayExample {
  // variable d'état publique
  uint[] public myArray;

  // Fonction getter générée par le compilateur
  /*
  function myArray(uint i) returns (uint) {
      return myArray[i];
  }
  */

  // fonction retournant une array complète
  function getArray() returns (uint[] memory) {
      return myArray;
  }
}

Maintenant vous pouvez utiliser getArray() pour récupérer le tableau entier, au lieu de myArray(i), qui retourne un seul élément par appel.

L’exemple suivant est plus complexe:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract Complex {
    struct Data {
        uint a;
        bytes3 b;
        mapping (uint => uint) map;
        uint[3] c;
        uint[] d;
        bytes e;
    }
    mapping (uint => mapping(bool => Data[])) public data;
}

It generates a function of the following form. The mapping and arrays (with the exception of byte arrays) in the struct are omitted because there is no good way to select individual struct members or provide a key for the mapping:

function data(uint arg1, bool arg2, uint arg3)
    public
    returns (uint a, bytes3 b, bytes memory e)
{
    a = data[arg1][arg2][arg3].a;
    b = data[arg1][arg2][arg3].b;
    e = data[arg1][arg2][arg3].e;
}

Modificateurs de fonctions

Les modificateurs peuvent être utilisés pour modifier facilement le comportement des fonctions. Par exemple, ils peuvent vérifier automatiquement une condition avant d’exécuter la fonction.

Les modificateurs sont des propriétés héritables des contrats et peuvent être redéfinis dans les contrats dérivés, mais seulement s’ils sont indiqués virtual. Pour plus de détails, voir Modifier Overriding.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

contract owned {
    constructor() { owner = payable(msg.sender); }
    address payable owner;

    // Ce contrat ne définit qu'un modificateur mais ne l'utilise pas:
    // il sera utilisé dans les contrats dérivés.
    // Le corps de la fonction est inséré à l'endroit où le symbole spécial
    // `_;` apparaît dans la définition d'un modificateur.
    // Cela signifie que si le propriétaire appelle cette fonction, la fonction
    // est exécutée et dans le cas contraire, une exception est
    // levée.
    modifier onlyOwner {
        require(
            msg.sender == owner,
            "Only owner can call this function."
        );
        _;
    }
}

contract mortal is owned {
    // Ce contrat hérite du modificateur `onlyOwner` de `owned`
    // et l'applique à la fonction `close`, qui
    // cause que les appels à `close` n'ont un effet que s'il
    // sont passés par le propriétaire enregistré.
    function close() public onlyOwner {
        selfdestruct(owner);
    }
}

contract priced {
    // Les modificateurs peuvent prendre des arguments:
    modifier costs(uint price) {
        if (msg.value >= price) {
            _;
        }
    }
}

contract Register is priced, owned {
    mapping (address => bool) registeredAddresses;
    uint price;

    constructor(uint initialPrice) { price = initialPrice; }

    // Il est important de fournir également le
    // mot-clé `payable` ici, sinon la fonction
    // rejettera automatiquement tous les Ethers qui lui sont envoyés.
    function register() public payable costs(price) {
        registeredAddresses[msg.sender] = true;
    }

    function changePrice(uint _price) public onlyOwner {
        price = _price;
    }
}

contract Mutex {
    bool locked;
    modifier noReentrancy() {
        require(
            !locked,
            "Reentrant call."
        );
        locked = true;
        _;
        locked = false;
    }

    /// Cette fonction est protégée par un mutex, ce qui signifie que
    /// les appels entrants à partir de `msg.sender.call` ne peuvent pas rappeler `f`.
    /// L'instruction `return 7` assigne 7 à la valeur de retour, mais en même temps
    /// exécute l'instruction `locked = false` dans le modificateur.
    function f() public noReentrancy returns (uint) {
        (bool success,) = msg.sender.call("");
        require(success);
        return 7;
    }
}

<<<<<<< HEAD Plusieurs modificateurs sont appliqués à une fonction en les spécifiant dans une liste séparée par des espaces et sont évalués dans l’ordre présenté. ======= If you want to access a modifier m defined in a contract C, you can use C.m to reference it without virtual lookup. It is only possible to use modifiers defined in the current contract or its base contracts. Modifiers can also be defined in libraries but their use is limited to functions of the same library.

Multiple modifiers are applied to a function by specifying them in a whitespace-separated list and are evaluated in the order presented. >>>>>>> 47d77931747aba8e364452537d989b795df7ca04

Modifiers cannot implicitly access or change the arguments and return values of functions they modify. Their values can only be passed to them explicitly at the point of invocation.

Explicit returns from a modifier or function body only leave the current modifier or function body. Return variables are assigned and control flow continues after the _ in the preceding modifier.

Avertissement

Dans une version antérieure de Solidity, les instructions return des fonctions ayant des modificateurs se comportaient différemment.

<<<<<<< HEAD Les retours explicites d’un modificateur ou d’un corps de fonction ne laissent que le modificateur ou le corps de fonction courant. Les variables de retour sont affectées et le flow de contrôle continue après le « _ » dans le modificateur précédent. ======= An explicit return from a modifier with return; does not affect the values returned by the function. The modifier can, however, choose not to execute the function body at all and in that case the return variables are set to their default values just as if the function had an empty body.

The _ symbol can appear in the modifier multiple times. Each occurrence is replaced with the function body. >>>>>>> 47d77931747aba8e364452537d989b795df7ca04

Des expressions arbitraires sont autorisées pour les arguments du modificateur et dans ce contexte, tous les symboles visibles depuis la fonction sont visibles dans le modificateur. Les symboles introduits dans le modificateur ne sont pas visibles dans la fonction (car ils peuvent changer en cas de redéfinition).

Variables d’état constantes et immutables

Les variables d’état peuvent être déclarées comme constantes ou immutable. Dans les deux cas, ces variables ne peuvent être modifiées après la construction du contrat. Dans ce cas, elles doivent être assignées à partir d’une expression constante au moment de la compilation. Pour les variables constant, la valeur doit être connue à la compilation. Pour les variables immutable, les variables peuvent être assognées jusqu’à la construction.

It is also possible to define constant variables at the file level.

Le compilateur ne réserve pas d’emplacement de stockage pour ces variables, et chaque occurrence est remplacée par l’expression constante correspondante.

Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.

Tous les types de constantes ne sont pas implémentés pour le moment. Les seuls types pris en charge sont chaines de caractères (uniquement pour les constantes) et types valeur.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.4;

uint constant X = 32**22 + 8;

contract C {
    string constant TEXT = "abc";
    bytes32 constant MY_HASH = keccak256("abc");
    uint immutable decimals;
    uint immutable maxBalance;
    address immutable owner = msg.sender;

    constructor(uint _decimals, address _reference) {
        decimals = _decimals;
        // L'assignement à des immutables peut même accéder à l'environnement
        maxBalance = _reference.balance;
    }

    function isBalanceTooHigh(address _other) public view returns (bool) {
        return _other.balance > maxBalance;
    }
}

Constant

Pour les variables constantes, les valeurs doivent être assignées à partir d’une expression constante au moment de la compilation et doit être assignée à la déclaration. Toute expression qui accède au stockage, aux données de la blockchain (par exemple address(this).balance ou block.number) ou les données d’exécution (msg.value ou gasleft()) ou les appels vers des contrats externes sont interdits. Les expressions qui peuvent avoir un effet secondaire sur l’allocation de mémoire sont autorisées, mais celles qui peuvent avoir un effet secondaire sur d’autres objets mémoire ne le sont pas. Les fonctions intégrées keccak256, sha256, ripemd160, ecrecover, addmod et mulmod sont autorisées (même si des contrats externes sont appelés).

La raison pour laquelle on autorise les effets secondaires sur l’allocateur de mémoire est qu’il devrait être possible de construire des objets complexes comme par exemple des tables de consultation. Cette fonctionnalité n’est pas encore entièrement utilisable.

Immutable

Variables declared as immutable are a bit less restricted than those declared as constant: Immutable variables can be assigned an arbitrary value in the constructor of the contract or at the point of their declaration. They can be assigned only once and can, from that point on, be read even during construction time.

The contract creation code generated by the compiler will modify the contract’s runtime code before it is returned by replacing all references to immutables by the values assigned to the them. This is important if you are comparing the runtime code generated by the compiler with the one actually stored in the blockchain.

Note

Immutables that are assigned at their declaration are only considered initialized once the constructor of the contract is executing. This means you cannot initialize immutables inline with a value that depends on another immutable. You can do this, however, inside the constructor of the contract.

This is a safeguard against different interpretations about the order of state variable initialization and constructor execution, especially with regards to inheritance.

Fonctions

Functions can be defined inside and outside of contracts.

Functions outside of a contract, also called « free functions », always have implicit internal visibility. Their code is included in all contracts that call them, similar to internal library functions.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

function sum(uint[] memory _arr) pure returns (uint s) {
    for (uint i = 0; i < _arr.length; i++)
        s += _arr[i];
}

contract ArrayExample {
    bool found;
    function f(uint[] memory _arr) public {
        // This calls the free function internally.
        // The compiler will add its code to the contract.
        uint s = sum(_arr);
        require(s >= 10);
        found = true;
    }
}

Note

Functions defined outside a contract are still always executed in the context of a contract. They still have access to the variable this, can call other contracts, send them Ether and destroy the contract that called them, among other things. The main difference to functions defined inside a contract is that free functions do not have direct access to storage variables and functions not in their scope.

Function Parameters and Return Variables

Functions take typed parameters as input and may, unlike in many other languages, also return an arbitrary number of values as output.

Function Parameters

Function parameters are declared the same way as variables, and the name of unused parameters can be omitted.

For example, if you want your contract to accept one kind of external call with two integers, you would use something like the following:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Simple {
    uint sum;
    function taker(uint _a, uint _b) public {
        sum = _a + _b;
    }
}

Function parameters can be used as any other local variable and they can also be assigned to.

Note

An external function cannot accept a multi-dimensional array as an input parameter. This functionality is possible if you enable the ABI coder v2 by adding pragma abicoder v2; to your source file.

An internal function can accept a multi-dimensional array without enabling the feature.

Return Variables

Function return variables are declared with the same syntax after the returns keyword.

For example, suppose you want to return two results: the sum and the product of two integers passed as function parameters, then you use something like:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Simple {
    function arithmetic(uint _a, uint _b)
        public
        pure
        returns (uint o_sum, uint o_product)
    {
        o_sum = _a + _b;
        o_product = _a * _b;
    }
}

The names of return variables can be omitted. Return variables can be used as any other local variable and they are initialized with their default value and have that value until they are (re-)assigned.

You can either explicitly assign to return variables and then leave the function as above, or you can provide return values (either a single or multiple ones) directly with the return statement:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract Simple {
    function arithmetic(uint _a, uint _b)
        public
        pure
        returns (uint o_sum, uint o_product)
    {
        return (_a + _b, _a * _b);
    }
}

If you use an early return to leave a function that has return variables, you must provide return values together with the return statement.

Note

You cannot return some types from non-internal functions, notably multi-dimensional dynamic arrays and structs. If you enable the ABI coder v2 by adding pragma abicoder v2; to your source file then more types are available, but mapping types are still limited to inside a single contract and you cannot transfer them.

Returning Multiple Values

When a function has multiple return types, the statement return (v0, v1, ..., vn) can be used to return multiple values. The number of components must be the same as the number of return variables and their types have to match, potentially after an implicit conversion.

State Mutability

Fonctions View

Les fonctions peuvent être déclarées view, auquel cas elles promettent de ne pas modifier l’état.

Note

Si la cible EVM du compilateur est Byzantium ou plus récent (par défaut), l’opcode STATICCALL est utilisé pour les fonctions view qui imposent à l’état de rester non modifié lors de l’exécution EVM. Pour les librairies, on utilise les fonctions view et DELEGATECALL parce qu’il n’y a pas de DELEGATECALL et STATICCALL combinés. Cela signifie que les fonctions view de librairies n’ont pas de contrôles d’exécution qui empêchent les modifications d’état. Cela ne devrait pas avoir d’impact négatif sur la sécurité car le code de librairies est généralement connu au moment de la compilation et le vérificateur statique effectue les vérifications au moment de la compilation.

Les déclarations suivantes sont considérées comme une modification de l’état :

  1. Ecrire dans les variables d’état.

  2. Emettre des événements.

  3. Création d’autres contrats.

  4. Utiliser selfdestruct.

  5. Envoyer des Ethers par des appels.

  6. Appeler une fonction qui n’est pas marquée view ou pure.

  7. Utilisation d’appels bas niveau.

  8. Utilisation d’assembleur inline qui contient certains opcodes.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    function f(uint a, uint b) public view returns (uint) {
        return a * (b + 42) + block.timestamp;
    }
}

Note

constant sur les fonctions était un alias de view, mais cela a été abandonné dans la version 0.5.0.

Note

Les méthodes Getter sont automatiquement marquées view.

Note

Avant la version 0.5.0, le compilateur n’utilisait pas l’opcode STATICCALL. pour les fonctions view. Cela permettait de modifier l’état des fonctions view grâce à l’utilisation de conversions de type explicites non valides. En utilisant STATICCALL pour les fonctions view, les modifications de la fonction sont évités au niveau de l’EVM.

Fonctions Pure

Les fonctions peuvent être déclarées pures, auquel cas elles promettent de ne pas lire ou modifier l’état. In particular, it should be possible to evaluate a pure function at compile-time given only its inputs and msg.data, but without any knowledge of the current blockchain state. This means that reading from immutable variables can be a non-pure operation.

Note

Si la cible EVM du compilateur est Byzantium ou plus récente (par défaut), on utilise l’opcode STATICCALL, ce qui ne garantit pas que l’état ne soit pas lu, mais au moins qu’il ne soit pas modifié.

En plus de la liste des modificateurs d’état expliqués ci-dessus, sont considérés comme des lectures de l’état :

  1. Lecture des variables d’état.

  2. Accéder à address(this).balance ou <address>.balance.

  3. Accéder à l’un des membres de block, tx, msg (à l’exception de msg.sig et msg.data).

  4. Appeler une fonction qui n’est pas marquée pure.

  5. Utilisation d’assembleur inline qui contient certains opcodes.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;

contract C {
    function f(uint a, uint b) public pure returns (uint) {
        return a * (b + 42);
    }
}

Note

Avant la version 0.5.0, le compilateur n’utilisait pas l’opcode STATICCALL pour les fonctions pure. Cela permettait de modifier l’état des fonctions pures en utilisant des conversions de type explicites invalides. En utilisant STATICCALL pour des fonctions pures, les modifications de l’état sont empêchées au niveau de l’EVM.

Avertissement

Il n’est pas possible d’empêcher les fonctions de lire l’état au niveau de l’EVM, il est seulement possible de les empêcher d’écrire dans l’état (c’est-à-dire que seul « view » peut être exécuté au niveau de l’EVM, pure ne peut pas).

Avertissement

Avant la version 0.4.17, le compilateur n’appliquait pas le fait que pure ne lisait pas l’état. Il s’agit d’un contrôle de type à la compilation, qui peut être contourné en effectuant des conversions explicites invalides entre les types de contrats, parce que le compilateur peut vérifier que le type de contrat ne fait pas d’opérations de changement d’état, mais il ne peut pas vérifier que le contrat qui sera appelé à l’exécution est effectivement de ce type.

Special Functions

Receive Ether Function

A contract can have at most one receive function, declared using receive() external payable { ... } (without the function keyword). This function cannot have arguments, cannot return anything and must have external visibility and payable state mutability. It can be virtual, can override and can have modifiers.

The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception.

In the worst case, the receive function can only rely on 2300 gas being available (for example when send or transfer is used), leaving little room to perform other operations except basic logging. The following operations will consume more gas than the 2300 gas stipend:

  • Writing to storage

  • Creating a contract

  • Calling an external function which consumes a large amount of gas

  • Sending Ether

Avertissement

Contracts that receive Ether directly (without a function call, i.e. using send or transfer) but do not define a receive Ether function or a payable fallback function throw an exception, sending back the Ether (this was different before Solidity v0.4.0). So if you want your contract to receive Ether, you have to implement a receive Ether function (using payable fallback functions for receiving Ether is not recommended, since it would not fail on interface confusions).

Avertissement

A contract without a receive Ether function can receive Ether as a recipient of a coinbase transaction (aka miner block reward) or as a destination of a selfdestruct.

A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.

It also means that address(this).balance can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the receive Ether function).

Below you can see an example of a Sink contract that uses function receive.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
    event Received(address, uint);
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }
}

Fonction de repli

Un contrat peut avoir exactement une fonction dwe repli fallback, declared using either fallback () external [payable] or fallback (bytes calldata _input) external [payable] returns (bytes memory _output) (both without the function keyword). This function must have external visibility. A fallback function can be virtual, can override and can have modifiers.

The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable.

If the version with parameters is used, _input will contain the full data sent to the contract (equal to msg.data) and can return data in _output. The returned data will not be ABI-encoded. Instead it will be returned without modifications (not even padding).

In the worst case, if a payable fallback function is also used in place of a receive function, it can only rely on 2300 gas being available (see receive Ether function for a brief description of the implications of this).

Comme toute fonction, la fonction de fallback peut exécuter des opérations complexes tant que suffisamment de gas lui est transmis.

Note

If you want to decode the input data, you can check the first four bytes for the function selector and then you can use abi.decode together with the array slice syntax to decode ABI-encoded data: (c, d) = abi.decode(_input[4:], (uint256, uint256)); Note that this should only be used as a last resort and proper functions should be used instead.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;

contract Test {
    uint x;
    // This function is called for all messages sent to
    // this contract (there is no other function).
    // Sending Ether to this contract will cause an exception,
    // because the fallback function does not have the `payable`
    // modifier.
    fallback() external { x = 1; }
}

contract TestPayable {
    uint x;
    uint y;
    // This function is called for all messages sent to
    // this contract, except plain Ether transfers
    // (there is no other function except the receive function).
    // Any call with non-empty calldata to this contract will execute
    // the fallback function (even if Ether is sent along with the call).
    fallback() external payable { x = 1; y = msg.value; }

    // This function is called for plain Ether transfers, i.e.
    // for every call with empty calldata.
    receive() external payable { x = 2; y = msg.value; }
}

contract Caller {
    function callTest(Test test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1.

        // address(test) will not allow to call ``send`` directly, since ``test`` has no payable
        // fallback function.
        // It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
        address payable testPayable = payable(address(test));

        // If someone sends Ether to that contract,
        // the transfer will fail, i.e. this returns false here.
        return testPayable.send(2 ether);
    }

    function callTestPayable(TestPayable test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1 and test.y becoming 0.
        (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1 and test.y becoming 1.

        // If someone sends Ether to that contract, the receive function in TestPayable will be called.
        // Since that function writes to storage, it takes more gas than is available with a
        // simple ``send`` or ``transfer``. Because of that, we have to use a low-level call.
        (success,) = address(test).call{value: 2 ether}("");
        require(success);
        // results in test.x becoming == 2 and test.y becoming 2 ether.

        return true;
    }
}

Surcharge de fonctions

Un contrat peut avoir plusieurs fonctions du même nom, mais avec des types de paramètres différents. Ce processus est appelé « surcharge » et s’applique également aux fonctions héritées. L’exemple suivant montre la surcharge de la fonction f dans le champ d’application du contrat A.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract A {
    function f(uint _in) public pure returns (uint out) {
        out = _in;
    }

    function f(uint _in, bool _really) public pure returns (int out) {
        if (_really)
            out = int(_in);
    }
}

Des fonctions surchargées sont également présentes dans l’interface externe. C’est une erreur si deux fonctions visibles de l’extérieur diffèrent par leur type Solidity (ici A et B) mais pas par leur type extérieur (ici address).

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

// Ceci ne compile pas
contract A {
    function f(B _in) public pure returns (B out) {
        out = _in;
    }

    function f(A _in) public pure returns (B out) {
        out = B(address(_in));
    }
}

contract B {
}

Les deux fonctions f surchargées ci-dessus acceptent des addresses du point de vue de l’ABI, mais ces adresses sont considérées comme différents types en Solidity.

Résolution des surcharges et concordance des arguments

Les fonctions surchargées sont sélectionnées en faisant correspondre les déclarations de fonction dans le scope actuel aux arguments fournis dans l’appel de fonction. La fonction évaluée est choisie si tous les arguments peuvent être implicitement convertis en types attendus. S’il y a plusieurs fonctions correspondantes, la résolution échoue.

Note

Le type des valeurs retournées par la fonction n’est pas pris en compte dans la résolution des surcharges.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract A {
    function f(uint8 _in) public pure returns (uint8 out) {
        out = _in;
    }

    function f(uint256 _in) public pure returns (uint256 out) {
        out = _in;
    }
}

L’appel de f(50) créerait une erreur de type puisque 50 peut être implicitement converti à la fois en type uint8 et uint256. D’un autre côté, f(256) se résoudrait à f(uint256) car 256 ne peut pas être implicitement converti en uint8.

Événements

Les événements Solidity autorisent une abstraction en plus de la fonctionnalité de journalisation de l’EVM. Les applications peuvent souscrire à et écouter ces événements via l’interface RPC d’un client Ethereum.

Les événements sont des membres héritables des contrats. Lorsque vous les appelez, ils font en sorte que les arguments soient stockés dans le journal des transactions - une structure de données spéciale dans la blockchain. Ces logs sont associés à l’adresse du contrat, sont incorporés dans la blockchain et y restent tant qu’un bloc est accessible (pour toujours à partir des versions Frontier et Homestead, mais cela peut changer avec Serenity). Le journal et ses données d’événement ne sont pas accessibles depuis les contrats (pas même depuis le contrat qui les a créés).

Il est possible de demander une simple vérification de paiement (SPV) pour les logs, de sorte que si une entité externe fournit un contrat avec une telle vérification, elle peut vérifier que le log existe réellement dans la blockchain. Vous devez fournir des en-têtes (headers) de bloc car le contrat ne peut voir que les 256 derniers hashs de blocs.

Vous pouvez ajouter l’attribut indexed à un maximum de trois paramètres qui les ajoute à une structure de données spéciale appelée « topics » au lieu de la partie data du log. Si vous utilisez des types « référence » (tableaux, y compris les string et bytes) comme arguments indexés, leurs hashs Keccak-256 sont stockés comme topic à la place, car un topic ne peut contenir qu’un seul mot (32 octets).

Tous les paramètres sans l’attribut indexed sont ABI-encoded dans la partie données du log.

Les topics vous permettent de rechercher des événements, par exemple lors du filtrage d’une séquence de blocs pour certains événements. Vous pouvez également filtrer les événements par l’adresse du contrat qui les a émis.

Par exemple, le code ci-dessous utilise web3.js subscribe("logs") method pour filtrer les logs qui correspondent à un sujet avec une certaine valeur d’adresse :

var options = {
    fromBlock: 0,
    address: web3.eth.defaultAccount,
    topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null]
};
web3.eth.subscribe('logs', options, function (error, result) {
    if (!error)
        console.log(result);
})
    .on("data", function (log) {
        console.log(log);
    })
    .on("changed", function (log) {
});

Le hash de la signature de l’event est l’un des topics, sauf si vous avez déclaré l’événement avec le spécificateur anonymous. Cela signifie qu’il n’est pas possible de filtrer des événements anonymes spécifiques par leur nom, you can only filter by the contract address. The advantage of anonymous events is that they are cheaper to deploy and call. It also allows you to declare four indexed arguments rather than three.

Note

Since the transaction log only stores the event data and not the type, you have to know the type of the event, including which parameter is indexed and if the event is anonymous in order to correctly interpret the data. In particular, it is possible to « fake » the signature of another event using an anonymous event.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.9.0;

contract ClientReceipt {
    event Deposit(
        address indexed _from,
        bytes32 indexed _id,
        uint _value
    );

    function deposit(bytes32 _id) public payable {
        // Les événements sont émis à l'aide de `emit`, suivi du
        // nom de l'événement et des arguments
        // (le cas échéant) entre parenthèses. Une telle invocation
        // (même profondément imbriquée) peut être détectée à partir de
        // l'API JavaScript en filtrant `Deposit`.
        emit Deposit(msg.sender, _id, msg.value);
    }
}

L’utilisation dans l’API JavaScript est la suivante :

var abi = /* abi telle que génerée par le compilateur */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* adresse */);

var depositEvent = clientReceipt.Deposit();

// inspecter les eventuels changements
depositEvent.watch(function(error, result){
    // le résultat contient des arguments et topics non indexés
    // passées à l'appel de `Deposit`.
    if (!error)
        console.log(result);
});


// Ou passez une fonction pour ecouter dès maintenant
var depositEvent = clientReceipt.Deposit(function(error, result) {
    if (!error)
        console.log(result);
});

La sortie du code ci-dessus ressemble à (trimmée):

{
   "returnValues": {
       "_from": "0x1111…FFFFCCCC",
       "_id": "0x50…sd5adb20",
       "_value": "0x420042"
   },
   "raw": {
       "data": "0x7f…91385",
       "topics": ["0xfd4…b4ead7", "0x7f…1a91385"]
   }
}

Additional Resources for Understanding Events

Errors and the Revert Statement

Errors in Solidity provide a convenient and gas-efficient way to explain to the user why an operation failed. They can be defined inside and outside of contracts (including interfaces and libraries).

They have to be used together with the revert statement which causes all changes in the current call to be reverted and passes the error data back to the caller.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

/// Insufficient balance for transfer. Needed `required` but only
/// `available` available.
/// @param available balance available.
/// @param required requested amount to transfer.
error InsufficientBalance(uint256 available, uint256 required);

contract TestToken {
    mapping(address => uint) balance;
    function transfer(address to, uint256 amount) public {
        if (amount > balance[msg.sender])
            revert InsufficientBalance({
                available: balance[msg.sender],
                required: amount
            });
        balance[msg.sender] -= amount;
        balance[to] += amount;
    }
    // ...
}

Errors cannot be overloaded or overridden but are inherited. The same error can be defined in multiple places as long as the scopes are distinct. Instances of errors can only be created using revert statements.

The error creates data that is then passed to the caller with the revert operation to either return to the off-chain component or catch it in a try/catch statement. Note that an error can only be caught when coming from an external call, reverts happening in internal calls or inside the same function cannot be caught.

If you do not provide any parameters, the error only needs four bytes of data and you can use NatSpec as above to further explain the reasons behind the error, which is not stored on chain. This makes this a very cheap and convenient error-reporting feature at the same time.

More specifically, an error instance is ABI-encoded in the same way as a function call to a function of the same name and types would be and then used as the return data in the revert opcode. This means that the data consists of a 4-byte selector followed by ABI-encoded data. The selector consists of the first four bytes of the keccak256-hash of the signature of the error type.

Note

It is possible for a contract to revert with different errors of the same name or even with errors defined in different places that are indistinguishable by the caller. For the outside, i.e. the ABI, only the name of the error is relevant, not the contract or file where it is defined.

The statement require(condition, "description"); would be equivalent to if (!condition) revert Error("description") if you could define error Error(string). Note, however, that Error is a built-in type and cannot be defined in user-supplied code.

Similarly, a failing assert or similar conditions will revert with an error of the built-in type Panic(uint256).

Note

Error data should only be used to give an indication of failure, but not as a means for control-flow. The reason is that the revert data of inner calls is propagated back through the chain of external calls by default. This means that an inner call can « forge » revert data that looks like it could have come from the contract that called it.

Héritage

Solidity supporte l’héritage multiple en copiant du code, incluant le polymorphisme.

Polymorphism means that a function call (internal and external) always executes the function of the same name (and parameter types) in the most derived contract in the inheritance hierarchy. This has to be explicitly enabled on each function in the hierarchy using the virtual and override keywords. See Function Overriding for more details.

It is possible to call functions further up in the inheritance hierarchy internally by explicitly specifying the contract using ContractName.functionName() or using super.functionName() if you want to call the function one level higher up in the flattened inheritance hierarchy (see below).

Lorsqu’un contrat hérite d’autres contrats, un seul contrat est créé dans la blockchain et le code de tous les contrats de base est copié dans le contrat créé. This means that all internal calls to functions of base contracts also just use internal function calls (super.f(..) will use JUMP and not a message call).

State variable shadowing is considered as an error. A derived contract can only declare a state variable x, if there is no visible state variable with the same name in any of its bases.

Le système général d’héritage est très similaire à celui de Python, surtout en ce qui concerne l’héritage multiple, mais il y a aussi quelques differences.

Les détails sont donnés dans l’exemple suivant.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;


contract Owned {
    constructor() { owner = payable(msg.sender); }
    address payable owner;
}


// Utilisez `is` pour dériver d'un autre contrat. Les contrats dérivés peuvent accéder à tous les membres non privés, y compris les fonctions internes et les variables d'état. Il n'est cependant pas possible d'y accéder de l'extérieur via `this`.
contract Destructible is Owned {
    // The keyword `virtual` means that the function can change
    // its behaviour in derived classes ("overriding").
    function destroy() virtual public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}


// Ces contrats abstraits ne sont fournis que pour faire connaître l'interface au compilateur. Notez la fonction sans corps. Si un contrat n'implémente pas toutes les fonctions, il ne peut être utilisé que comme interface.
abstract contract Config {
    function lookup(uint id) public virtual returns (address adr);
}


abstract contract NameReg {
    function register(bytes32 name) public virtual;
    function unregister() public virtual;
}


// L'héritage multiple est possible. Notez que `owned` est aussi une classe de base de `mortal`, pourtant il n'y a qu'une seule instance de `owned` (comme pour l'héritage virtuel en C++).
contract Named is Owned, Destructible {
    constructor(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }

    // Les fonctions peuvent être remplacées par une autre fonction ayant le même nom et le même nombre/type d'entrées.  Si la fonction de surcharge a différents types de paramètres de sortie, cela provoque une erreur.
    // Les appels de fonction locaux et les appels de fonction basés sur la messagerie tiennent compte de ces dérogations.
    // If you want the function to override, you need to use the
    // `override` keyword. You need to specify the `virtual` keyword again
    // if you want this function to be overridden again.
    function destroy() public virtual override {
        if (msg.sender == owner) {
            Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
            NameReg(config.lookup(1)).unregister();
            // It is still possible to call a specific
            // overridden function.
            Destructible.destroy();
        }
    }
}


// Si un constructeur prend un argument, il doit être fourni dans l'en-tête (ou dans le constructeur du contrat dérivé (voir ci-dessous)).
contract PriceFeed is Owned, Destructible, Named("GoldFeed") {
    function updateInfo(uint newInfo) public {
        if (msg.sender == owner) info = newInfo;
    }

    // Here, we only specify `override` and not `virtual`.
    // This means that contracts deriving from `PriceFeed`
    // cannot change the behaviour of `destroy` anymore.
    function destroy() public override(Destructible, Named) { Named.destroy(); }
    function get() public view returns(uint r) { return info; }

    uint info;
}

Notez que ci-dessus, nous appelons Destructible.destroy() pour « transmettre » la demande de destruction. La façon dont cela est fait est problématique, comme vu dans l’exemple suivant:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract owned {
    constructor() { owner = payable(msg.sender); }
    address payable owner;
}

contract Destructible is owned {
    function destroy() public virtual {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is Destructible {
    function destroy() public virtual override { /* do cleanup 1 */ Destructible.destroy(); }
}

contract Base2 is Destructible {
    function destroy() public virtual override { /* do cleanup 2 */ Destructible.destroy(); }
}

contract Final is Base1, Base2 {
    function destroy() public override(Base1, Base2) { Base2.destroy(); }
}

Un appel à Final.destroy() appellera Base2.destroy puisque nous le demandons explicitement dans l’override, mais cet appel évitera Base1.destroy. La solution à ce problème est d’utiliser super:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract owned {
    constructor() { owner = payable(msg.sender); }
    address payable owner;
}

contract Destructible is owned {
    function destroy() virtual public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is Destructible {
    function destroy() public virtual override { /* do cleanup 1 */ super.destroy(); }
}


contract Base2 is Destructible {
    function destroy() public virtual override { /* do cleanup 2 */ super.destroy(); }
}

contract Final is Base1, Base2 {
    function destroy() public override(Base1, Base2) { super.destroy(); }
}

Si Base2 appelle une fonction de super, elle n’appelle pas simplement cette fonction sur un de ses contrats de base. Elle appelle plutôt cette fonction sur le prochain contrat de base dans le graph d’héritage final, donc elle appellera Base1.destroy() (notez que la séquence d’héritage finale est – en commençant par le contrat le plus dérivé : Final, Base2, Base1, Destructible, owned). La fonction réelle qui est appelée lors de l’utilisation de super n’est pas connue dans le contexte de la classe où elle est utilisée, bien que son type soit connu. Il en va de même pour la recherche de méthodes virtuelles ordinaires.

Function Overriding

Base functions can be overridden by inheriting contracts to change their behavior if they are marked as virtual. The overriding function must then use the override keyword in the function header. The overriding function may only change the visibility of the overridden function from external to public. The mutability may be changed to a more strict one following the order: nonpayable can be overridden by view and pure. view can be overridden by pure. payable is an exception and cannot be changed to any other mutability.

The following example demonstrates changing mutability and visibility:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Base
{
    function foo() virtual external view {}
}

contract Middle is Base {}

contract Inherited is Middle
{
    function foo() override public pure {}
}

For multiple inheritance, the most derived base contracts that define the same function must be specified explicitly after the override keyword. In other words, you have to specify all base contracts that define the same function and have not yet been overridden by another base contract (on some path through the inheritance graph). Additionally, if a contract inherits the same function from multiple (unrelated) bases, it has to explicitly override it:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract Base1
{
    function foo() virtual public {}
}

contract Base2
{
    function foo() virtual public {}
}

contract Inherited is Base1, Base2
{
    // Derives from multiple bases defining foo(), so we must explicitly
    // override it
    function foo() public override(Base1, Base2) {}
}

An explicit override specifier is not required if the function is defined in a common base contract or if there is a unique function in a common base contract that already overrides all other functions.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract A { function f() public pure{} }
contract B is A {}
contract C is A {}
// No explicit override required
contract D is B, C {}

More formally, it is not required to override a function (directly or indirectly) inherited from multiple bases if there is a base contract that is part of all override paths for the signature, and (1) that base implements the function and no paths from the current contract to the base mentions a function with that signature or (2) that base does not implement the function and there is at most one mention of the function in all paths from the current contract to that base.

In this sense, an override path for a signature is a path through the inheritance graph that starts at the contract under consideration and ends at a contract mentioning a function with that signature that does not override.

If you do not mark a function that overrides as virtual, derived contracts can no longer change the behaviour of that function.

Note

Functions with the private visibility cannot be virtual.

Note

Functions without implementation have to be marked virtual outside of interfaces. In interfaces, all functions are automatically considered virtual.

Note

Starting from Solidity 0.8.8, the override keyword is not required when overriding an interface function, except for the case where the function is defined in multiple bases.

Public state variables can override external functions if the parameter and return types of the function matches the getter function of the variable:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract A
{
    function f() external view virtual returns(uint) { return 5; }
}

contract B is A
{
    uint public override f;
}

Note

While public state variables can override external functions, they themselves cannot be overridden.

Modifier Overriding

Function modifiers can override each other. This works in the same way as function overriding (except that there is no overloading for modifiers). The virtual keyword must be used on the overridden modifier and the override keyword must be used in the overriding modifier:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract Base
{
    modifier foo() virtual {_;}
}

contract Inherited is Base
{
    modifier foo() override {_;}
}

In case of multiple inheritance, all direct base contracts must be specified explicitly:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

contract Base1
{
    modifier foo() virtual {_;}
}

contract Base2
{
    modifier foo() virtual {_;}
}

contract Inherited is Base1, Base2
{
    modifier foo() override(Base1, Base2) {_;}
}

Constructeurs

Un constructeur est une fonction optionnelle déclarée avec le mot-clé constructeur qui est exécuté lors de la création du contrat, et où vous pouvez exécuter le code d’initialisation du contrat.

Avant l’exécution du code constructeur, les variables d’état sont initialisées à leur valeur spécifiée si vous les initialisez en ligne, ou leur default value si vous ne le faites pas.

Après l’exécution du constructeur, le code final du contrat est déployé dans la chaîne de blocs. Le déploiement du code coûte du gas supplémentaire linéairement à la longueur du code. Ce code inclut toutes les fonctions qui font partie de l’interface publique et toutes les fonctions qui sont accessibles à partir de là par des appels de fonctions. Il n’inclut pas le code constructeur ni les fonctions internes qui ne sont appelées que par le constructeur.

S’il n’y a pas de constructeur, le contrat assumera le constructeur par défaut, ce qui est équivalent à constructor() {}. Par exemple :

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

abstract contract A {
    uint public a;

    constructor(uint _a) {
        a = _a;
    }
}

contract B is A(1) {
    constructor() {}
}

You can use internal parameters in a constructor (for example storage pointers). In this case, the contract has to be marked abstract, because these parameters cannot be assigned valid values from outside but only through the constructors of derived contracts.

Attention

Avant 0.4.22, ont été définis comme des fonctions portant le même nom que le contrat. Cette syntaxe a été dépréciée et n’est plus autorisée dans la version 0.5.0.

Avertissement

Prior to version 0.7.0, you had to specify the visibility of constructors as either internal or public.

Arguments des Constructeurs de Base

Les constructeurs de tous les contrats de base seront appelés selon les règles de linéarisation expliquées ci-dessous. Si les constructeurs de base ont des arguments, les contrats dérivés doivent les spécifier tous. Cela peut se faire de deux façons:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Base {
    uint x;
    constructor(uint _x) { x = _x; }
}

// Either directly specify in the inheritance list...
contract Derived1 is Base(7) {
    constructor() {}
}

// or through a "modifier" of the derived constructor.
contract Derived2 is Base {
    constructor(uint _y) Base(_y * _y) {}
}

One way is directly in the inheritance list (is Base(7)). The other is in the way a modifier is invoked as part of the derived constructor (Base(_y * _y)). The first way to do it is more convenient if the constructor argument is a constant and defines the behaviour of the contract or describes it. The second way has to be used if the constructor arguments of the base depend on those of the derived contract. Arguments have to be given either in the inheritance list or in modifier-style in the derived constructor. Specifying arguments in both places is an error.

If a derived contract does not specify the arguments to all of its base contracts” constructors, it will be abstract.

Multiple Inheritance and Linearization

Languages that allow multiple inheritance have to deal with several problems. One is the Diamond Problem. Solidity is similar to Python in that it uses « C3 Linearization » to force a specific order in the directed acyclic graph (DAG) of base classes. This results in the desirable property of monotonicity but disallows some inheritance graphs. Especially, the order in which the base classes are given in the is directive is important: You have to list the direct base contracts in the order from « most base-like » to « most derived ». Note that this order is the reverse of the one used in Python.

Another simplifying way to explain this is that when a function is called that is defined multiple times in different contracts, the given bases are searched from right to left (left to right in Python) in a depth-first manner, stopping at the first match. If a base contract has already been searched, it is skipped.

In the following code, Solidity will give the error « Linearization of inheritance graph impossible ».

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;

contract X {}
contract A is X {}
// This will not compile
contract C is A, X {}

The reason for this is that C requests X to override A (by specifying A, X in this order), but A itself requests to override X, which is a contradiction that cannot be resolved.

Due to the fact that you have to explicitly override a function that is inherited from multiple bases without a unique override, C3 linearization is not too important in practice.

One area where inheritance linearization is especially important and perhaps not as clear is when there are multiple constructors in the inheritance hierarchy. The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract’s constructor. For example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Base1 {
    constructor() {}
}

contract Base2 {
    constructor() {}
}

// Constructors are executed in the following order:
//  1 - Base1
//  2 - Base2
//  3 - Derived1
contract Derived1 is Base1, Base2 {
    constructor() Base1() Base2() {}
}

// Constructors are executed in the following order:
//  1 - Base2
//  2 - Base1
//  3 - Derived2
contract Derived2 is Base2, Base1 {
    constructor() Base2() Base1() {}
}

// Constructors are still executed in the following order:
//  1 - Base2
//  2 - Base1
//  3 - Derived3
contract Derived3 is Base2, Base1 {
    constructor() Base1() Base2() {}
}

Inheriting Different Kinds of Members of the Same Name

It is an error when any of the following pairs in a contract have the same name due to inheritance:
  • a function and a modifier

  • a function and an event

  • an event and a modifier

As an exception, a state variable getter can override an external function.

Abstract Contracts

Contracts need to be marked as abstract when at least one of their functions is not implemented. Contracts may be marked as abstract even though all functions are implemented.

This can be done by using the abstract keyword as shown in the following example. Note that this contract needs to be defined as abstract, because the function utterance() was defined, but no implementation was provided (no implementation body { } was given).

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

abstract contract Feline {
    function utterance() public virtual returns (bytes32);
}

Such abstract contracts can not be instantiated directly. This is also true, if an abstract contract itself does implement all defined functions. The usage of an abstract contract as a base class is shown in the following example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

abstract contract Feline {
    function utterance() public pure virtual returns (bytes32);
}

contract Cat is Feline {
    function utterance() public pure override returns (bytes32) { return "miaow"; }
}

If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it needs to be marked as abstract as well.

Note that a function without implementation is different from a Function Type even though their syntax looks very similar.

Example of function without implementation (a function declaration):

function foo(address) external returns (address);

Example of a declaration of a variable whose type is a function type:

function(address) external returns (address) foo;

Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and facilitating patterns like the Template method and removing code duplication. Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say « any child of mine must implement this method ».

Note

Abstract contracts cannot override an implemented virtual function with an unimplemented one.

Interfaces

Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:

  • They cannot inherit from other contracts, but they can inherit from other interfaces.

  • All declared functions must be external.

  • They cannot declare a constructor.

  • They cannot declare state variables.

  • They cannot declare modifiers.

Some of these restrictions might be lifted in the future.

Interfaces are basically limited to what the Contract ABI can represent, and the conversion between the ABI and an interface should be possible without any information loss.

Interfaces are denoted by their own keyword:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;

interface Token {
    enum TokenType { Fungible, NonFungible }
    struct Coin { string obverse; string reverse; }
    function transfer(address recipient, uint amount) external;
}

Contracts can inherit interfaces as they would inherit other contracts.

All functions declared in interfaces are implicitly virtual and any functions that override them do not need the override keyword. This does not automatically mean that an overriding function can be overridden again - this is only possible if the overriding function is marked virtual.

Interfaces can inherit from other interfaces. This has the same rules as normal inheritance.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;

interface ParentA {
    function test() external returns (uint256);
}

interface ParentB {
    function test() external returns (uint256);
}

interface SubInterface is ParentA, ParentB {
    // Must redefine test in order to assert that the parent
    // meanings are compatible.
    function test() external override(ParentA, ParentB) returns (uint256);
}

Types defined inside interfaces and other contract-like structures can be accessed from other contracts: Token.TokenType or Token.Coin.

Libraries

Libraries are similar to contracts, but their purpose is that they are deployed only once at a specific address and their code is reused using the DELEGATECALL (CALLCODE until Homestead) feature of the EVM. This means that if library functions are called, their code is executed in the context of the calling contract, i.e. this points to the calling contract, and especially the storage from the calling contract can be accessed. As a library is an isolated piece of source code, it can only access state variables of the calling contract if they are explicitly supplied (it would have no way to name them, otherwise). Library functions can only be called directly (i.e. without the use of DELEGATECALL) if they do not modify the state (i.e. if they are view or pure functions), because libraries are assumed to be stateless. In particular, it is not possible to destroy a library.

Note

Until version 0.4.20, it was possible to destroy libraries by circumventing Solidity’s type system. Starting from that version, libraries contain a mechanism that disallows state-modifying functions to be called directly (i.e. without DELEGATECALL).

Libraries can be seen as implicit base contracts of the contracts that use them. They will not be explicitly visible in the inheritance hierarchy, but calls to library functions look just like calls to functions of explicit base contracts (using qualified access like L.f()). Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and types stored in memory will be passed by reference and not copied. To realize this in the EVM, the code of internal library functions that are called from a contract and all functions called from therein will at compile time be included in the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL.

Note

The inheritance analogy breaks down when it comes to public functions. Calling a public library function with L.f() results in an external call (DELEGATECALL to be precise). In contrast, A.f() is an internal call when A is a base contract of the current contract.

The following example illustrates how to use libraries (but using a manual method, be sure to check out using for for a more advanced example to implement a set).

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;


// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data {
    mapping(uint => bool) flags;
}

library Set {
    // Note that the first parameter is of type "storage
    // reference" and thus only its storage address and not
    // its contents is passed as part of the call.  This is a
    // special feature of library functions.  It is idiomatic
    // to call the first parameter `self`, if the function can
    // be seen as a method of that object.
    function insert(Data storage self, uint value)
        public
        returns (bool)
    {
        if (self.flags[value])
            return false; // already there
        self.flags[value] = true;
        return true;
    }

    function remove(Data storage self, uint value)
        public
        returns (bool)
    {
        if (!self.flags[value])
            return false; // not there
        self.flags[value] = false;
        return true;
    }

    function contains(Data storage self, uint value)
        public
        view
        returns (bool)
    {
        return self.flags[value];
    }
}


contract C {
    Data knownValues;

    function register(uint value) public {
        // The library functions can be called without a
        // specific instance of the library, since the
        // "instance" will be the current contract.
        require(Set.insert(knownValues, value));
    }
    // In this contract, we can also directly access knownValues.flags, if we want.
}

Of course, you do not have to follow this way to use libraries: they can also be used without defining struct data types. Functions also work without any storage reference parameters, and they can have multiple storage reference parameters and in any position.

The calls to Set.contains, Set.insert and Set.remove are all compiled as calls (DELEGATECALL) to an external contract/library. If you use libraries, be aware that an actual external function call is performed. msg.sender, msg.value and this will retain their values in this call, though (prior to Homestead, because of the use of CALLCODE, msg.sender and msg.value changed, though).

The following example shows how to use types stored in memory and internal functions in libraries in order to implement custom types without the overhead of external function calls:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

struct bigint {
    uint[] limbs;
}

library BigInt {
    function fromUint(uint x) internal pure returns (bigint memory r) {
        r.limbs = new uint[](1);
        r.limbs[0] = x;
    }

    function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) {
        r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
        uint carry = 0;
        for (uint i = 0; i < r.limbs.length; ++i) {
            uint a = limb(_a, i);
            uint b = limb(_b, i);
            unchecked {
                r.limbs[i] = a + b + carry;

                if (a + b < a || (a + b == type(uint).max && carry > 0))
                    carry = 1;
                else
                    carry = 0;
            }
        }
        if (carry > 0) {
            // too bad, we have to add a limb
            uint[] memory newLimbs = new uint[](r.limbs.length + 1);
            uint i;
            for (i = 0; i < r.limbs.length; ++i)
                newLimbs[i] = r.limbs[i];
            newLimbs[i] = carry;
            r.limbs = newLimbs;
        }
    }

    function limb(bigint memory _a, uint _limb) internal pure returns (uint) {
        return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
    }

    function max(uint a, uint b) private pure returns (uint) {
        return a > b ? a : b;
    }
}

contract C {
    using BigInt for bigint;

    function f() public pure {
        bigint memory x = BigInt.fromUint(7);
        bigint memory y = BigInt.fromUint(type(uint).max);
        bigint memory z = x.add(y);
        assert(z.limb(1) > 0);
    }
}

It is possible to obtain the address of a library by converting the library type to the address type, i.e. using address(LibraryName).

As the compiler does not know the address where the library will be deployed, the compiled hex code will contain placeholders of the form __$30bbc0abd4d6364515865950d3e0d10953$__. The placeholder is a 34 character prefix of the hex encoding of the keccak256 hash of the fully qualified library name, which would be for example libraries/bigint.sol:BigInt if the library was stored in a file called bigint.sol in a libraries/ directory. Such bytecode is incomplete and should not be deployed. Placeholders need to be replaced with actual addresses. You can do that by either passing them to the compiler when the library is being compiled or by using the linker to update an already compiled binary. See Library Linking for information on how to use the commandline compiler for linking.

In comparison to contracts, libraries are restricted in the following ways:

  • they cannot have state variables

  • they cannot inherit nor be inherited

  • they cannot receive Ether

  • they cannot be destroyed

(These might be lifted at a later point.)

Function Signatures and Selectors in Libraries

While external calls to public or external library functions are possible, the calling convention for such calls is considered to be internal to Solidity and not the same as specified for the regular contract ABI. External library functions support more argument types than external contract functions, for example recursive structs and storage pointers. For that reason, the function signatures used to compute the 4-byte selector are computed following an internal naming schema and arguments of types not supported in the contract ABI use an internal encoding.

The following identifiers are used for the types in the signatures:

  • Value types, non-storage string and non-storage bytes use the same identifiers as in the contract ABI.

  • Non-storage array types follow the same convention as in the contract ABI, i.e. <type>[] for dynamic arrays and <type>[M] for fixed-size arrays of M elements.

  • Non-storage structs are referred to by their fully qualified name, i.e. C.S for contract C { struct S { ... } }.

  • Storage pointer mappings use mapping(<keyType> => <valueType>) storage where <keyType> and <valueType> are the identifiers for the key and value types of the mapping, respectively.

  • Other storage pointer types use the type identifier of their corresponding non-storage type, but append a single space followed by storage to it.

The argument encoding is the same as for the regular contract ABI, except for storage pointers, which are encoded as a uint256 value referring to the storage slot to which they point.

Similarly to the contract ABI, the selector consists of the first four bytes of the Keccak256-hash of the signature. Its value can be obtained from Solidity using the .selector member as follows:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.14 <0.9.0;

library L {
    function f(uint256) external {}
}

contract C {
    function g() public pure returns (bytes4) {
        return L.f.selector;
    }
}

Call Protection For Libraries

As mentioned in the introduction, if a library’s code is executed using a CALL instead of a DELEGATECALL or CALLCODE, it will revert unless a view or pure function is called.

The EVM does not provide a direct way for a contract to detect whether it was called using CALL or not, but a contract can use the ADDRESS opcode to find out « where » it is currently running. The generated code compares this address to the address used at construction time to determine the mode of calling.

More specifically, the runtime code of a library always starts with a push instruction, which is a zero of 20 bytes at compilation time. When the deploy code runs, this constant is replaced in memory by the current address and this modified code is stored in the contract. At runtime, this causes the deploy time address to be the first constant to be pushed onto the stack and the dispatcher code compares the current address against this constant for any non-view and non-pure function.

This means that the actual code stored on chain for a library is different from the code reported by the compiler as deployedBytecode.

Using For

The directive using A for B; can be used to attach library functions (from the library A) to any type (B) in the context of a contract. These functions will receive the object they are called on as their first parameter (like the self variable in Python).

The effect of using A for *; is that the functions from the library A are attached to any type.

In both situations, all functions in the library are attached, even those where the type of the first parameter does not match the type of the object. The type is checked at the point the function is called and function overload resolution is performed.

The using A for B; directive is active only within the current contract, including within all of its functions, and has no effect outside of the contract in which it is used. The directive may only be used inside a contract, not inside any of its functions.

Let us rewrite the set example from the Libraries in this way:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;


// This is the same code as before, just without comments
struct Data { mapping(uint => bool) flags; }

library Set {
    function insert(Data storage self, uint value)
        public
        returns (bool)
    {
        if (self.flags[value])
            return false; // already there
        self.flags[value] = true;
        return true;
    }

    function remove(Data storage self, uint value)
        public
        returns (bool)
    {
        if (!self.flags[value])
            return false; // not there
        self.flags[value] = false;
        return true;
    }

    function contains(Data storage self, uint value)
        public
        view
        returns (bool)
    {
        return self.flags[value];
    }
}


contract C {
    using Set for Data; // this is the crucial change
    Data knownValues;

    function register(uint value) public {
        // Here, all variables of type Data have
        // corresponding member functions.
        // The following function call is identical to
        // `Set.insert(knownValues, value)`
        require(knownValues.insert(value));
    }
}

It is also possible to extend elementary types in that way:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.8 <0.9.0;

library Search {
    function indexOf(uint[] storage self, uint value)
        public
        view
        returns (uint)
    {
        for (uint i = 0; i < self.length; i++)
            if (self[i] == value) return i;
        return type(uint).max;
    }
}

contract C {
    using Search for uint[];
    uint[] data;

    function append(uint value) public {
        data.push(value);
    }

    function replace(uint _old, uint _new) public {
        // This performs the library function call
        uint index = data.indexOf(_old);
        if (index == type(uint).max)
            data.push(_new);
        else
            data[index] = _new;
    }
}

Note that all external library calls are actual EVM function calls. This means that if you pass memory or value types, a copy will be performed, even of the self variable. The only situation where no copy will be performed is when storage reference variables are used or when internal library functions are called.