Types

Solidity est un langage à typage statique, ce qui signifie que le type de chaque variable (état et locale) doit être spécifié. Solidity propose plusieurs types élémentaires qui peuvent être combinés pour former des types complexes.

De plus, les types peuvent interagir entre eux dans des expressions contenant des opérateurs. Pour une liste synthétique des différents opérateurs, voir Order of Precedence of Operators.

The concept of « undefined » or « null » values does not exist in Solidity, but newly declared variables always have a default value dependent on its type. To handle any unexpected values, you should use the revert function to revert the whole transaction, or return a tuple with a second bool value denoting success.

Value Types

The following types are also called value types because variables of these types will always be passed by value, i.e. they are always copied when they are used as function arguments or in assignments.

Booleans

bool: The possible values are constants true and false.

Operators:

  • ! (logical negation)
  • && (logical conjunction, « and »)
  • || (logical disjunction, « or »)
  • == (equality)
  • != (inequality)

The operators || and && apply the common short-circuiting rules. This means that in the expression f(x) || g(y), if f(x) evaluates to true, g(y) will not be evaluated even if it may have side-effects.

Entiers

int / uint: Entiers signés et non-signés de différentes tailles. Les mots-clé uint8 à uint256 par pas de 8 (entier non signé de 8 à 256 bits) et int8 à int256. uint et int sont des alias de uint256 et int256, respectivement.

Opérateurs:

  • Comparaisons: <=, <, ==, !=, >=, > (retournent un bool)
  • Opérateurs binaires: &, |, ^ (ou exclusif binaire), ~ (négation binaire)
  • Opérateurs de décalage: << (décalage vers la gauche), >> (décalage vers la droite)
  • Opérateurs arithmétiques: +, -, l” opérateur unaire -, *, /, % (modulo), ** (exponentiation)

For an integer type X, you can use type(X).min and type(X).max to access the minimum and maximum value representable by the type.

Avertissement

Integers in Solidity are restricted to a certain range. For example, with uint32, this is 0 up to 2**32 - 1. If the result of some operation on those numbers does not fit inside this range, it is truncated. These truncations can have serious consequences that you should be aware of and mitigate against.

Comparaisons

La valeur d’une comparaison est celle obtenue en comparant la valeur entière.

Opérations binaires

Les opérations binaires sont effectuées sur la représentation du nombre par complément à deux<https://fr.wikipedia.org/wiki/Compl%C3%A9ment_%C3%A0_deux>. Cela signifie que, par exemple, ~int256(0) == int256(-1).

Décalages

  • For positive and negative x values, x << y is equivalent to x * 2**y.
  • For positive x values, x >> y is equivalent to x / 2**y.
  • For negative x values, x >> y is equivalent to (x + 1) / 2**y - 1 (which is the same as dividing x by 2**y while rounding down towards negative infinity).
  • In all cases, shifting by a negative y throws a runtime exception.

Décaler d’un nombre négatif de bits déclenche une exception.

Avertissement

Avant la version 0.5.0.0, un décalage vers la droite x >> y pour un x négatif était équivalent à x / 2**y, c’est-à-dire que les décalages vers la droite étaient arrondis vers zéro plutôt que vers l’infini négatif.

Addition, Soustraction et Multiplication

L’addition, la soustraction et la multiplication ont la sémantique habituelle. Ils utilisent également la représentation du complément de deux, ce qui signifie, par exemple, que uint256(0) - uint256(1) == 2**256 - 1. Vous devez tenir compte de ces débordements (« overflows ») pour la conception de contrats sûrs.

L’expression x équivaut à (T(0) - x)T est le type de x. Cela signifie que -x ne sera pas négatif si le type de x est un type entier non signé. De plus, x peut être positif si x est négatif. Il y a une autre mise en garde qui découle également de la représentation en compléments de deux:

int x = -2**255;
assert(-x == x);

Cela signifie que même si un nombre est négatif, vous ne pouvez pas supposer que sa négation sera positive.

Division

Puisque le type du résultat d’une opération est toujours le type d’un des opérandes, la division sur les entiers donne toujours un entier. Dans Solidity, la division s’arrondit vers zéro. Cela signifie que int256(-5) / int256(2) == int256(-2).

Notez qu’en revanche, la division sur les littéraux donne des valeurs fractionnaires de précision arbitraire.

Note

La division par zéra cause un échec d”assert.

Modulo

L’opération modulo a % n donne le reste r après la division de l’opérande a par l’opérande n, où q = int(a / n) et r = a - (n * q). Cela signifie que modulo donne le même signe que son opérande gauche (ou zéro) et a % n == -(abs(a) % n) est valable pour un a négatif:

  • int256(5) % int256(2) == int256(1)
  • int256(5) % int256(-2) == int256(1)
  • int256(-5) % int256(2) == int256(-1)
  • int256(-5) % int256(-2) == int256(-1)

Note

La division par zéra cause un échec d”assert.

Exponentiation

l’exponentiation n’est disponible que p[our les types signés. Veillez à ce que les types que vous utilisez soient suffisamment grands pour conserver le résultat et vous préparer à un éventuel effet d’enroulage (wrapping/int overflow).

Note

0**0 est défini par l’EVM comme étant 1.

Nombre à virgule fixe

Avertissement

Les numéros à point fixe ne sont pas encore entièrement pris en charge par Solidity. Ils peuvent être déclarés, mais ne peuvent pas être affectés à ou de.

fixed / ufixed: Nombre à virgule fixe signés et non-signés de taille variable. Les mots-clés ufixedMxN et fixedMxN, où M représente le nombre de bits pris par le type et N représente combien de décimales sont disponibles. M doit être divisible par 8 et peut aller de 8 à 256 bits. N doit être compris entre 0 et 80, inclusivement. ufixed et fixed sont des alias pour ufixed128x18 et fixed128x18, respectivement.

Opérateurs:

  • Comparaisons: <=, <, ==, !=, >=, > (évalue à bool)
  • Operateurs arithmétiques: +, -, l’opérateur unaire -, *, /, % (modulo)

Note

La principale différence entre les nombres à virgule flottante (float``et ``double dans de nombreux langages, plus précisément les nombres IEEE 754) et les nombres à virgule fixe est que le nombre de bits utilisés pour l’entier et la partie fractionnaire (la partie après le point décimal) est flexible dans le premier, alors qu’il est strictement défini dans le second. Généralement, en virgule flottante, presque tout l’espace est utilisé pour représenter le nombre, alors que seul un petit nombre de bits définit où se trouve le point décimal.

Adresses

Le type d’adresse se décline en deux versions, qui sont en grande partie identiques :

  • address : Contient une valeur de 20 octets (taille d’une adresse Ethereum).
  • address payable : Même chose que « adresse », mais avec les membres additionnels transfert et envoi.

L’idée derrière cette distinction est que l”address payable est une adresse à laquelle vous pouvez envoyer de l’éther, alors qu’une simple address ne peut être envoyée de l’éther.

Conversions de type :

Les conversions implicites de address payable à address sont autorisées, tandis que les conversions de address à address payable ne sont pas possibles.

Note

La seule façon d’effectuer une telle conversion est d’utiliser une conversion intermédiaire en uint160.

Les adresses littérales peuvent être implicitement converties en address payable.

Les conversions explicites vers et à partir de address sont autorisées pour les entiers, les entiers littéraux, les bytes20 et les types de contrats avec les réserves suivantes : Les conversions sous la forme address payable(x) ne sont pas permises. Au lieu de cela, le résultat d’une conversion sous forme adresse(x) donne une address payable si x est un contrat disposant d’une fonction par défaut (fallback) payable, ou si x est de type entier, bytes fixes, ou littéral. Sinon, l’adresse obtenue sera de type address. Dans les fonctions de signature externes, address est utilisé à la fois pour le type address``et ``address payable.

Note

Il se peut fort bien que vous n’ayez pas à vous soucier de la distinction entre address et address payable et que vous utilisiez simplement address partout. Par exemple, si vous utilisez la fonction withdrawal pattern, vous pouvez (et devriez) stocker l’adresse elle-même comme address, parce que vous invoquez la fonction transfer sur
msg.sender, qui est une address payable.

Opérateurs :

  • <=, <, ==, !=, >= and >

Avertissement

Si vous convertissez un type qui utilise une taille d’octet plus grande en address, par exemple bytes32, alors l’adresse est tronquée.

Pour réduire l’ambiguïté de conversion à partir de la version 0.4.24 du compilateur vous force à rendre la troncature explicite dans la conversion. Prenons par exemple l’adresse 0x1111222222323333434444545555666666777777778888999999AAAABBBBBBCCDDDDEEFEFFFFFFCC.

Vous pouvez utiliser address(uint160(octets20(b))), ce qui donne 0x1111212222323333434444545555666677778888889999aAaaa, ou vous pouvez utiliser address(uint160(uint256(b))), ce qui donne 0x777777888888999999AaAAbBbbCcccddDdeeeEfFFfCcCcCc.

Note

La distinction entre address``et ``address payable a été introduite avec la version 0.5.0.
À partir de cette version également, les contrats ne dérivent pas du type d’adresse, mais peuvent toujours être convertis explicitement en adresse  » ou à  » adresse payable « , s’ils ont une fonction par défaut payable.

Membres de Address

Pour une liste des membres de address, voir Membres du type address.

  • balance et transfer.

Il est possible d’interroger le solde d’une adresse en utilisant la propriété balance et d’envoyer des Ether (en unités de wei) à une adresse payable à l’aide de la fonction transfert :

address payable x = address(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

La fonction transfer échoue si le solde du contrat en cours n’est pas suffisant ou si le transfert d’Ether est rejeté par le compte destinataire. La fonction transfert s’inverse en cas d’échec.

Note

Si x est une adresse de contrat, son code (plus précisément : sa Fonction de repli, si présente) sera exécutée avec l’appel transfer (c’est une caractéristique de l’EVM et ne peut être empêché). Si cette exécution échoue ou s’il n’y a plus de gas, le transfert d’Ether sera annulé et le contrat en cours s’arrêtera avec une exception.

  • send

send est la contrepartie de bas niveau du transfer. Si l’exécution échoue, le contrat en cours ne s’arrêtera pas avec une exception, mais send retournera false.

Avertissement

Il y a certains dangers à utiliser la fonction send : Le transfert échoue si la profondeur de la stack atteint 1024 (cela peut toujours être forcé par l’appelant) et il échoue également si le destinataire manque de gas. Donc, afin d’effectuer des transferts d’Ether en toute sécurité, vérifiez toujours la valeur de retour de send, utilisez transfer ou mieux encore  : utilisez un modèle où le destinataire retire l’argent.

  • call, delegatecall et staticcall

Afin de s’interfacer avec des contrats qui ne respectent pas l’ABI, ou d’obtenir un contrôle plus direct sur l’encodage, les fonctions call, delegatecall et staticcall sont disponibles. Elles prennent tous pour argument un seul bytes memory comme entrée et retournent la condition de succès (en tant que bool) et les données (bytes memory). Les fonctions abi.encoder, abi.encoderPacked, abi.encoderWithSelector et abi.encoderWithSignature peuvent être utilisées pour coder des données structurées.

Exemple:

bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);

Avertissement

Toutes ces fonctions sont des fonctions de bas niveau et doivent être utilisées avec précaution.
Plus précisément, tout contrat inconnu peut être malveillant et si vous l’appelez, vous transférez le contrôle à ce contrat qui, à son tour, peut revenir dans votre contrat, donc soyez prêt à modifier les variables de votre état. quand l’appel revient. La façon habituelle d’interagir avec d’autres contrats est d’appeler une fonction sur un objet contract (x.f())..
:: note::
Les versions précédentes de Solidity permettaient à ces fonctions de recevoir des arguments arbitraires et de traiter différemment un premier argument de type bytes4. Ces cas rares ont été supprimés dans la version 0.5.0.

Il est possible de régler le gas fourni avec le modificateur .gas():

namReg.call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));

De même, la valeur en Ether fournie peut également être contrôlée: ::

nameReg.call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));

Enfin, ces modificateurs peuvent être combinés. Leur ordre n’a pas d’importance:

nameReg.call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));

De la même manière, la fonction delegatecall peut être utilisée: la différence est que seul le code de l’adresse donnée est utilisé, tous les autres aspects (stockage, balance,…) sont repris du contrat actuel. Le but de delegatecall est d’utiliser du code de bibliothèque qui est stocké dans un autre contrat. L’utilisateur doit s’assurer que la disposition du stockage dans les deux contrats est adaptée à l’utilisation de delegatecall.

Note

Avant Homestead, il n’existait qu’une variante limitée appelée callcode qui ne donnait pas accès aux valeurs originales msg.sender et msg.value. Cette fonction a été supprimée dans la version 0.5.0.

Depuis Byzantium, staticcall peut aussi être utilisé. C’est fondamentalement la même chose que call, mais reviendra en arrière si la fonction appelée modifie l’état d’une manière ou d’une autre.

Les trois fonctions call, delegatecall``et ``staticcall sont des fonctions de très bas niveau et ne devraient être utilisées qu’en dernier recours car elles brisent la sécurité de type de Solidity.

L’option .gas() est disponible sur les trois méthodes, tandis que l’option .value() n’est pas supportée pour delegatecall.

Note

Tous les contrats pouvant être convertis en type address, il est possible d’interroger le solde du contrat en cours en utilisant address(this).balance.

Types Contrat

Chaque contrat définit son propre type. Vous pouvez implicitement convertir des contrats en contrats dont ils héritent. Les contrats peuvent être explicitement convertis de et vers tous les autres types de contrats et le type address.

La conversion explicite vers et depuis le type address payable n’est possible que si le type de contrat dispose d’une fonction de repli payante. La conversion est toujours effectuée en utilisant address(x) et non address payable(x). Vous trouverez plus d’informations dans la section sur le type address.

Note

Avant la version 0.5.0, les contrats dérivaient directement du type address et il n’y avait aucune distinction entre address et address payable.

Si vous déclarez une variable locale de type contrat (MonContrat c), vous pouvez appeler des fonctions sur ce contrat. Prenez bien soin de l’assigner à un contrat d’un type correspondant.

Vous pouvez également instancier les contrats (ce qui signifie qu’ils sont nouvellement créés). Vous trouverez plus de détails dans la section “contrats de création”.

La représentation des données d’un contrat est identique à celle du type address et ce type est également utilisé dans l”ABI.

Les contrats ne supportent aucun opérateur.

Les membres du type contrat sont les fonctions externes du contrat, y compris les variables d’état publiques.

For a contract C you can use type(C) to access type information about the contract.

Tableaux d’octets de taille fixe

Les types valeur bytes1, bytes2, bytes3, …, bytes32 contiennent une séquence de 1 à 32 octets. byte est un alias de bytes1.

Opérateurs:

  • Comparaisons: <=, <, ==, !=, >=, > (retournent un bool)
  • Opérateurs binaires: &, |, ^ (ou exclusif binaire), ~ (négation binaire)
  • Opérateurs de décalage: << (décalage vers la gauche), >> (décalage vers la droite)
  • Accès par indexage: Si x estd e type bytesI, alors x[k] pour 0 <= k < I retourne le k ème byte (lecture seule).

L’opérateur de décalage travaille avec n’importe quel type d’entier comme opérande droite (mais retourne le type de l’opérande gauche), qui indique le nombre de bits à décaler. Le décalage d’un montant négatif entraîne une exception d’exécution.

Membres :

*.length` donne la longueur fixe du tableau d’octets (lecture seule).

Note

Le type byte[] est un tableau d’octets, mais en raison des règles de bourrage, il gaspille 31 octets d’espace pour chaque élément (sauf en storage). Il est préférable d’utiliser le type « bytes » à la place.

Tableaux dynamiques d’octets

bytes:
Tableau d’octets de taille dynamique, voir Tableaux. Ce n’est pas un type valeur !
string:
Chaîne codée UTF-8 de taille dynamique, voir Tableaux. Ce n’est pas un type valeur !

Adresses Littérales

Les caractères hexadécimaux qui réussissent un test de somme de contrôle d’adresse (« address checksum »), par exemple 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF sont de type address payable. Les nombres hexadécimaux qui ont entre 39 et 41 chiffres et qui ne passent pas le test de somme de contrôle produisent un avertissement et sont traités comme des nombres rationnels littéraux réguliers.

Note

Le format de some de contrôle multi-casse est décrit dans EIP-55.

Rationels et entiers littéraux

Les nombres entiers littéraux sont formés à partir d’une séquence de nombres compris entre 0 et 9 interprétés en décimal. Par exemple, 69 signifie soixante-neuf. Les littéraux octaux n’existent pas dans Solidity et les zéros précédant un nombre sont invalides.

Les fractions décimales sont formées par un . avec au moins un chiffre sur un côté. Exemples : 1.1, .1 `` et ``1.3.

La notation scientifique est également supportée, où la base peut avoir des fractions, alors que l’exposant ne le peut pas. Exemples : 2e10, -2e10, 2e-10, 2e-10, 2.5e1.

Les soulignements (underscore) peuvent être utilisés pour séparer les chiffres d’un nombre littéral numérique afin d’en faciliter la lecture. Par exemple, la décimale 123_000, l’hexadécimale 0x2eff_abde, la notation décimale scientifique 1_2e345_678 sont toutes valables. Les tirets de soulignement ne sont autorisés qu’entre deux chiffres et un seul tiret de soulignement consécutif est autorisé. Il n’y a pas de signification sémantique supplémentaire ajoutée à un nombre contenant des tirets de soulignement, les tirets de soulignement sont ignorés.

Les expressions littérales numériques conservent une précision arbitraire jusqu’à ce qu’elles soient converties en un type non littéral (c’est-à-dire en les utilisant avec une expression non littérale ou par une conversion explicite). Cela signifie que les calculs ne débordent pas (overflow) et que les divisions ne tronquent pas les expressions littérales des nombres.

Par exemple, (2**800 + 1) - 2**800 produit la constante 1 (de type uint8) bien que les résultats intermédiaires ne rentrent même pas dans la taille d’un mot machine. De plus, .5 * 8 donne l’entier 4 (bien que des nombres non entiers aient été utilisés entre les deux).

N’importe quel opérateur qui peut être appliqué aux nombres entiers peut également être appliqué aux expressions littérales des nombres tant que les opérandes sont des nombres entiers. Si l’un des deux est fractionnaire, les opérations sur bits sont interdites et l’exponentiation est interdite si l’exposant est fractionnaire (parce que cela pourrait résulter en un nombre non rationnel).

Avertissement

La dvision d’entiers littéraux tronquait dans les versions de Solidity avant la version 0.4.0, mais elle donne maintenant en un nombre rationnel, c’est-à-dire que 5 / 2 n’est pas égal à 2, mais à 2.5.

Note

Solidity a un type de nombre littéral pour chaque nombre rationnel.
Les nombres entiers littéraux et les nombres rationnels appartiennent à des types de nombres littéraux. De plus, toutes les expressions numériques littérales (c’est-à-dire les expressions qui ne contiennent que des nombres et des opérateurs) appartiennent à des types littéraux de nombres. Ainsi, les expressions littérales 1 + 2 et 2 + 1 appartiennent toutes deux au même type littéral de nombre pour le nombre rationnel numéro trois.

Note

Les expressions littérales numériques sont converties en caractères non littéraux dès qu’elles sont utilisées avec des expressions non littérales. Indépendamment des types, la valeur de l’expression assignée à b ci-dessous est évaluée en entier. Comme a est de type uint128, l’expression 2,5 + a doit cependant avoir un type. Puisqu’il n’y a pas de type commun pour les types 2.5 et uint128, le compilateur Solidity n’accepte pas ce code.

uint128 a = 1;
uint128 b = 2.5 + a + 0.5;

Chaines de caractères littérales

Les chaînes de caractères littérales sont écrites avec des guillemets simples ou doubles ("foo" ou 'bar'). Elles n’impliquent pas de zéro final comme en C ; foo représente trois octets, pas quatre. Comme pour les entiers littéraux, leur type peut varier, mais ils sont implicitement convertibles en bytes1, …, bytes32, ou s’ils conviennent, en bytes et en string.

Les chaînes de caractères littérales supportent les caractères d’échappement suivants :

  • \<newline> (échappe un réel caractère newline)
  • \\ (barre oblique)
  • \' (guillemet simple)
  • \" (guillemet double)
  • \b (backspace)
  • \f (form feed)
  • \n (newline)
  • \r (carriage return)
  • \t (tabulation horizontale)
  • \v (tabulation verticale)
  • \xNN (hex escape, see below)
  • \uNNNN (echapement d’unicode, voir ci-dessous)

\xNN prend une valeur hexadécimale et insère l’octet approprié, tandis que \uNNNNN prend un codepoint Unicode et insère une séquence UTF-8.

La chaîne de caractères de l’exemple suivant a une longueur de dix octets. Elle commence par un octet de newline, suivi d’une guillemet double, d’une guillemet simple, d’un caractère barre oblique inversée et ensuite (sans séparateur) de la séquence de caractères abcdef.

"\n\"\'\\abc\
def"

Tout terminateur de ligne unicode qui n’est pas une nouvelle ligne (i.e. LF, VF, FF, CR, NEL, LS, PS) est considéré comme terminant la chaîne littérale. Newline ne termine la chaîne littérale que si elle n’est pas précédée d’un \.

Hexadécimaux littéraux

Les caractères hexadécimaux sont précédées du mot-clé hex et sont entourées de guillemets simples ou doubles (hex"001122FF"). Leur contenu doit être une chaîne hexadécimale et leur valeur sera la représentation binaire de ces valeurs.

Les littéraux hexadécimaux se comportent comme chaînes de caractères littérales et ont les mêmes restrictions de convertibilité.

Énumérateurs

Les enum sont une façon de créer un type défini par l’utilisateur en Solidity. Ils sont explicitement convertibles de et vers tous les types d’entiers mais la conversion implicite n’est pas autorisée. La conversion explicite à partir d’un nombre entier vérifie au moment de l’exécution que la valeur se trouve à l’intérieur de la plage de l’enum et provoque une affirmation d’échec autrement. Un enum a besoin d’au moins un membre.

La représentation des données est la même que pour les énumérations en C : Les options sont représentées par des valeurs entières non signées à partir de 0.

pragma solidity >=0.4.16 <0.6.0;

contract test {
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    ActionChoices choice;
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;

    function setGoStraight() public {
        choice = ActionChoices.GoStraight;
    }

    // Comme le type enum ne fait pas partie de l' ABI, la signature de "getChoice"
    // sera automatoquement changée en "getChoice() returns (uint8)"
    // pour ce qui sort de Solidity. Le type entier utilisé est
    // assez grand pour contenir toutes valeurs, par exemple si vous en avez
    // plus de 256, ``uint16`` sera utilisé etc...
    function getChoice() public view returns (ActionChoices) {
        return choice;
    }

    function getDefaultChoice() public pure returns (uint) {
        return uint(defaultChoice);
    }
}

Types Fonction

Les types fonction sont les types des fonctions. Les variables du type fonction peuvent être passés et retournés pour transférer les fonctions vers et renvoyer les fonctions des appels de fonction. Les types de fonctions se déclinent en deux versions : les fonctions internes internal et les fonctions externes external :

Les fonctions internes ne peuvent être appelées qu’à l’intérieur du contrat en cours (plus précisément, à l’intérieur de l’unité de code en cours, qui comprend également les fonctions de bibliothèque internes et les fonctions héritées) car elles ne peuvent pas être exécutées en dehors du contexte du contrat actuel. L’appel d’une fonction interne est réalisé en sautant à son label d’entrée, tout comme lors de l’appel interne d’une fonction du contrat en cours.

Les fonctions externes se composent d’une adresse et d’une signature de fonction et peuvent être transférées et renvoyées à partir des appels de fonction externes.

Les types de fonctions sont notés comme suit: :

fonction (<types de paramètres>) {internal|external} {pure|view|payable][returns (<types de retour>)]

En contraste avec types de paramètres, les types de retour ne peuvent pas être vides - si le type de fonction ne retourne rien, toute la partie ``returns (<types de retour>)``doit être omise.

Par défaut, les fonctions sont de type internal, donc le mot-clé internal peut être omis. Notez que ceci ne s’applique qu’aux types de fonctions. La visibilité doit être spécifiée explicitement car les fonctions définies dans les contrats n’ont pas de valeur par défaut.

Conversions :

Une fonction de type external peut être explicitement convertie en address résultant en l’adresse du contrat de la fonction.

Un type de fonction A est implicitement convertible en un type de fonction B si et seulement si leurs types de paramètres sont identiques, leurs types de retour sont identiques, leurs propriétés internal/external sont identiques et la mutabilité d’état de A n’est pas plus restrictive que la mutabilité de l’état de B. En particulier :

  • Les fonctions pure peuvent être converties en fonctions view et non-payable.
  • Les fonctions view peuvent être converties en fonctions non-payable.
  • les fonctions payable peuvent être converties en fonctions non-payable.

Aucune autre conversion entre les types de fonction n’est possible.

La règle concernant les fonctions payable et non-payable peut prêter à confusion, mais essentiellement, si une fonction est payable, cela signifie qu’elle accepte aussi un paiement de zéro Ether, donc elle est également non-payable. D’autre part, une fonction non-payable rejettera l’Ether qui lui est envoyé, de sorte que les fonctions non-payable ne peuvent pas être converties en fonctions payable.

Si une variable de type fonction n’est pas initialisée, l’appel de celle-ci entraîne l’échec d’une assertion. Il en va de même si vous appelez une fonction après avoir utilisé delete dessus.

Si des fonctions de type external sont appelées d’en dehors du contexte de Solidity, ils sont traités comme le type function, qui code l’adresse suivie de l’identificateur de fonction ensemble dans un seul type bytes24.

Notez que les fonctions publiques du contrat actuel peuvent être utilisées à la fois comme une fonction interne et comme une fonction externe. Pour utiliser f comme fonction interne, utilisez simplement f, si vous voulez utiliser sa forme externe, utilisez this.f`.

Members:

External (or public) functions have the following members:

  • .address returns the address of the contract of the function.
  • .selector returns the ABI function selector
  • .gas(uint) returns a callable function object which, when called, will send the specified amount of gas to the target function. Deprecated - use {gas: ...} instead. See External Function Calls for more information.
  • .value(uint) returns a callable function object which, when called, will send the specified amount of wei to the target function. Deprecated - use {value: ...} instead. See External Function Calls for more information.

Example that shows how to use the members:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
// This will report a warning

contract Example {
    function f() public payable returns (bytes4) {
        assert(this.f.address == address(this));
        return this.f.selector;
    }

    function g() public {
        this.f.gas(10).value(800)();
        // New syntax:
        // this.f{gas: 10, value: 800}()
    }
}

Exemple d’utilisation des fonctions de type internal:

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

library ArrayUtils {
  // les fonctions internes peuvent être utilisées dams des fonctions
  // de librairies internes car elles partagent le même contexte
    function map(uint[] memory self, function (uint) pure returns (uint) f)
        internal
        pure
        returns (uint[] memory r)
    {
        r = new uint[](self.length);
        for (uint i = 0; i < self.length; i++) {
            r[i] = f(self[i]);
        }
    }

    function reduce(
        uint[] memory self,
        function (uint, uint) pure returns (uint) f
    )
        internal
        pure
        returns (uint r)
    {
        r = self[0];
        for (uint i = 1; i < self.length; i++) {
            r = f(r, self[i]);
        }
    }

    function range(uint length) internal pure returns (uint[] memory r) {
        r = new uint[](length);
        for (uint i = 0; i < r.length; i++) {
            r[i] = i;
        }
    }
}


contract Pyramid {
    using ArrayUtils for *;

    function pyramid(uint l) public pure returns (uint) {
        return ArrayUtils.range(l).map(square).reduce(sum);
    }

    function square(uint x) internal pure returns (uint) {
        return x * x;
    }

    function sum(uint x, uint y) internal pure returns (uint) {
        return x + y;
    }
}

Exemple d” usage de fonction external:

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


contract Oracle {
    struct Request {
        bytes data;
        function(uint) external callback;
    }

    Request[] private requests;
    event NewRequest(uint);

    function query(bytes memory data, function(uint) external callback) public {
        requests.push(Request(data, callback));
        emit NewRequest(requests.length - 1);
    }

    function reply(uint requestID, uint response) public {
        // Here goes the check that the reply comes from a trusted source
        requests[requestID].callback(response);
    }
}


contract OracleUser {
    Oracle constant private ORACLE_CONST = Oracle(0x1234567); // known contract
    uint private exchangeRate;

    function buySomething() public {
        ORACLE_CONST.query("USD", this.oracleResponse);
    }

    function oracleResponse(uint response) public {
        require(
            msg.sender == address(ORACLE_CONST),
            "Only oracle can call this."
        );
        exchangeRate = response;
    }
}

Note

Les fonctions lambda ou en in-line sont prévues mais pas encore prises en charge.

Types Référence

Les valeurs du type référence peuvent être modifiées par plusieurs noms différents. Comparez ceci avec les catégories de valeurs où vous obtenez une copie indépendante chaque fois qu’une variable de valeur est utilisée. Pour cette raison, les types référence doivent être traités avec plus d’attention que les types de valeur. Actuellement, les types référence comprennent les structures, les tableaux et les mappages. Si vous utilisez un type référence, vous devez toujours indiquer explicitement la zone de données où le type est enregistré : (dont la durée de vie est limitée à un appel de fonction), storage (l’emplacement où les variables d’état sont stockées) ou calldata (emplacement de données spécial qui contient les arguments de fonction, disponible uniquement pour les paramètres d’appel de fonction externe).

Une affectation ou une conversion de type qui modifie l’emplacement des données entraîne toujours une opération de copie automatique, alors que les affectations à l’intérieur du même emplacement de données ne copient que dans certains cas selon le type de stockage.

Emplacement des données

Chaque type référence, c’est-à-dire arrays (tableaux) et structs, comporte une annotation supplémentaire, la localisation des données, indiquant où elles sont stockées. Il y a trois emplacements de données : Memory, Storage et Calldata. Calldata n’est valable que pour les paramètres des fonctions de contrat externes et n’est nécessaire que pour ce type de paramètre. Calldata est une zone non modifiable, non persistante où les arguments de fonction sont stockés, et se comporte principalement comme memory.

Note

Avant la version 0.5.0, l’emplacement des données pouvait être omis, et était par défaut à des emplacements différents selon le type de variable, le type de fonction, etc.

Data location and assignment behaviour

La localisation des données n’est sont pas seulement pertinente pour la persistance des données, mais aussi pour la sémantique des affectations :

  • Les affectations entre le stockage et la mémoire (ou à partir des données de la calldata) créent toujours une copie indépendante.
  • Les affectations de mémoire à mémoire ne créent que des références. Cela signifie que les modifications d’une variable mémoire sont également visibles dans toutes les autres variables mémoire qui se réfèrent aux mêmes données.
  • Les affectations du stockage à une variable de stockage local n’affectent également qu’une référence.
  • En revanche, toutes les autres affectations au stockage sont toujours copiées. Les affectations à des variables d’état ou à des membres de variables locales de type structure de stockage, même si la variable locale elle-même n’est qu’une référence, constituent des exemples dans ce cas.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;

contract C {
    // The data location of x is storage.
    // This is the only place where the
    // data location can be omitted.
    uint[] x;

    // The data location of memoryArray is memory.
    function f(uint[] memory memoryArray) public {
        x = memoryArray; // works, copies the whole array to storage
        uint[] storage y = x; // works, assigns a pointer, data location of y is storage
        y[7]; // fine, returns the 8th element
        y.pop(); // fine, modifies x through y
        delete x; // fine, clears the array, also modifies y
        // The following does not work; it would need to create a new temporary /
        // unnamed array in storage, but storage is "statically" allocated:
        // y = memoryArray;
        // This does not work either, since it would "reset" the pointer, but there
        // is no sensible location it could point to.
        // delete y;
        g(x); // calls g, handing over a reference to x
        h(x); // calls h and creates an independent, temporary copy in memory
    }

    function g(uint[] storage) internal pure {}
    function h(uint[] memory) public pure {}
}

Tableaux

Les tableaux peuvent avoir une taille fixe à la compilation ou peuvent être dynamiques.

The type of an array of fixed size k and element type T is written as T[k], and an array of dynamic size as T[].

Un tableau de taille fixe k et de type d’élément T est écrit T[k], un tableau de taille dynamique T[].

Par exemple, un tableau de 5 tableaux dynamiques de uint est uint[][5] (notez que la notation est inversée par rapport à certains autres langages). Pour accéder au deuxième uint du troisième tableau dynamique, vous utilisez x[2][1] (les indexs commencent à zéro et l’accès fonctionne dans le sens inverse de la déclaration, c’est-à-dire que x[2] supprime un niveau dans le type de déclaration à partir de la droite).

Indices are zero-based, and access is in the opposite direction of the declaration.

Il y a peu de restrictions concernant l’élément contenu, il peut aussi être un autre tableau, un mappage ou une structure. Les restrictions générales s’appliquent, cependant, en ce sens que les mappages ne peuvent être utilisés que dans le storage et que les fonctions visibles au public nécessitent des paramètres qui sont des types reconnus par l”ABI types.

Il est possible de marquer les tableaux public et de demander à Solidity de créer un getter. L’index numérique deviendra un paramètre obligatoire pour le getter.

L’accès à un tableau après sa fin provoque un revert. Si vous voulez ajouter de nouveaux éléments, vous devez utiliser .push() ou augmenter le membre .length (voir ci-dessous).

Accessing an array past its end causes a failing assertion. Methods .push() and .push(value) can be used to append a new element at the end of the array, where .push() appends a zero-initialized element and returns a reference to it.

bytes and strings as Arrays

Les variables de type bytes et string sont des tableaux spéciaux. Un byte est semblable à un byte[], mais il est condensé en calldata et en mémoire. string est égal à bytes, mais ne permet pas l’accès à la longueur ou à l’index.

Solidity does not have string manipulation functions, but there are third-party string libraries. You can also compare two strings by their keccak256-hash using keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2)) and concatenate two strings using abi.encodePacked(s1, s2).

Il faut donc généralement préférer les bytes aux bytes[] car c’est moins cher à l’usage. En règle générale, utilisez bytes pour les données en octets bruts de longueur arbitraire et string pour les données de chaîne de caractères de longueur arbitraire (UTF-8). Si vous pouvez limiter la longueur à un certain nombre d’octets, utilisez toujours un des bytes1 à bytes32, car ils sont beaucoup moins chers également.

Note

Si vous voulez accéder à la représentation en octets d’une chaîne de caractères s, utilisez bytes(s).length / bytes(s)[7] ='x';. Gardez à l’esprit que vous accédez aux octets de bas niveau de la représentation UTF-8, et non aux caractères individuels !

Allouer des tableaux en mémoire

Vous pouvez utiliser le mot-clé new pour créer des tableaux dont la longueur dépend de la durée d’exécution en mémoire. Contrairement aux tableaux de stockage, il n’est pas possible de redimensionner les tableaux de mémoire (par exemple en les assignant au membre .length). Vous devez soit calculer la taille requise à l’avance, soit créer un nouveau tableau de mémoire et copier chaque élément.

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

contract C {
    function f(uint len) public pure {
        uint[] memory a = new uint[](7);
        bytes memory b = new bytes(len);
        assert(a.length == 7);
        assert(b.length == len);
        a[6] = 8;
    }
}

Tableaux littéraux / Inline Arrays

An array literal is a comma-separated list of one or more expressions, enclosed in square brackets ([...]). For example [1, a, f(3)]. There must be a common type all elements can be implicitly converted to. This is the elementary type of the array.

Array literals are always statically-sized memory arrays.

Le type d’un tableau littéral est un tableau mémoire de taille fixe dont le type de base est le type commun des éléments donnés. Le type de [1, 2, 3] est uint8[3] memory`, car le type de chacune de ces constantes est uint8. Pour cette raison, il est nécessaire de convertir le premier élément de l’exemple ci-dessus en uint.

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

contract C {
    function f() public pure {
        g([uint(1), 2, 3]);
    }
    function g(uint[3] memory) public pure {
        // ...
    }
}

Les tableaux de taille fixe ne peuvent pas être assignées à des tableaux de taille dynamique, c’est-à-dire que ce qui suit n’est pas possible :

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

// Ceci ne compile pas.
contract C {
    function f() public {
        // La ligne suivant provoque une erreur car uint[3] memory
        // ne peut pas être convertit en uint[] memory.
        uint[] memory x = [uint(1), 3, 4];
    }
}

Il est prévu de supprimer cette restriction à l’avenir, mais crée actuellement certaines complications en raison de la façon dont les tableaux sont transmis dans l’ABI.

Array Members

length:
Les tableaux ont un membre length qui contient leur nombre d’éléments.
La longueur des tableaux memory est fixe (mais dynamique, c’est-à-dire qu’elle peut dépendre des paramètres d’exécution) une fois qu’ils sont créés.
push():
Les tableaux de stockage dynamique et les bytes (et non string) ont une fonction membre appelée push que vous pouvez utiliser pour ajouter un élément à la fin du tableau. L’élément sera mis à zéro à l’initialisation. La fonction renvoie la nouvelle longueur.
push(x):
Dynamic storage arrays and bytes (not string) have a member function called push(x) that you can use to append a given element at the end of the array. The function returns nothing.
pop:
Les tableaux de stockage dynamique et les bytes (et non string) ont une fonction membre appelée pop que vous pouvez utiliser pour supprimer un élément à la fin du tableau. Ceci appelle aussi implicitement :ref:delete sur l’élément supprimé.

Note

L’augmentation de la longueur d’un tableau en storage a des coûts en gas constants parce qu’on suppose que le stockage est nul, alors que la diminution de la longueur a au moins un coût linéaire (mais dans la plupart des cas pire que linéaire), parce qu’elle inclut explicitement l’élimination des éléments supprimés comme si on appelait :ref:delete.

Note

To use arrays of arrays in external (instead of public) functions, you need to activate ABIEncoderV2.

Note

Dans les versions EVM antérieures à Byzantium, il n’était pas possible d’accéder au retour de tableaux dynamique à partir des appels de fonctions. Si vous appelez des fonctions qui retournent des tableaux dynamiques, assurez-vous d’utiliser un EVM qui est configuré en mode Byzantium.

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

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;
    // Notez que ce qui suit n'est pas une paire de tableaux dynamiques
    // mais un tableau tableau dynamique de paires (c'est-à-dire de
    // tableaux de taille fixe de longueur deux).
    // Pour cette raison, T[] est toujours un tableau dynamique
    // de T, même si T lui-même est un tableau.
    // L'emplacement des données pour toutes les variables d'état
    // est storage.
    bool[2][] m_pairsOfFlags;

    // newPairs est stocké en memory - seule possibilité
    // pour les arguments de fonction publique
    function setAllFlagPairs(bool[2][] memory newPairs) public {
        // l'assignation d' un tableau en storage implique la copie
        // de  ``newPairs`` et remplace l'array ``m_pairsOfFlags``.
        m_pairsOfFlags = newPairs;
    }

    struct StructType {
        uint[] contents;
        uint moreInfo;
    }
    StructType s;

    function f(uint[] memory c) public {
        // stocke un pointeur sur ``s`` dans ``g``
        StructType storage g = s;
        // change aussi ``s.moreInfo``.
        g.moreInfo = 2;
        // assigne une copie car ``g.contents`` n'est
        // pas une variable locale mais un membre
        // d'une variable locale
        g.contents = c;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) public {
        // accès à un index inexistant, déclenche une exception
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) public {
        // using push and pop is the only way to change the
        // length of an array
        if (newSize < m_pairsOfFlags.length) {
            while (m_pairsOfFlags.length > newSize)
                m_pairsOfFlags.pop();
        } else if (newSize > m_pairsOfFlags.length) {
            while (m_pairsOfFlags.length < newSize)
                m_pairsOfFlags.push();
        }
    }

    function clear() public {
        // these clear the arrays completely
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // identical effect here
        m_pairsOfFlags = new bool[2][](0);
    }

    bytes m_byteData;

    function byteArrays(bytes memory data) public {
        // le tableau de byte ("bytes") sont différents car stockés sans
        // padding mais peuvent être traités comme des ``uint8[]``
        m_byteData = data;
        for (uint i = 0; i < 7; i++)
            m_byteData.push();
        m_byteData[3] = 0x08;
        delete m_byteData[2];
    }

    function addFlag(bool[2] memory flag) public returns (uint) {
        m_pairsOfFlags.push(flag);
        return m_pairsOfFlags.length;
    }

    function createMemoryArray(uint size) public pure returns (bytes memory) {
        // Un tableau dynamique est créé via `new`:
        uint[2][] memory arrayOfPairs = new uint[2][](size);

        // Les tableaux littéraux sont toujours de taille statique
        // et en cas d' utilisation de littéraux uniquement, au moins
        // un type doit être spécifié.
        arrayOfPairs[0] = [uint(1), 2];

        // Créée un tableau dynamique de bytes:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(uint8(i));
        return b;
    }
}

Array Slices

Array slices are a view on a contiguous portion of an array. They are written as x[start:end], where start and end are expressions resulting in a uint256 type (or implicitly convertible to it). The first element of the slice is x[start] and the last element is x[end - 1].

If start is greater than end or if end is greater than the length of the array, an exception is thrown.

Both start and end are optional: start defaults to 0 and end defaults to the length of the array.

Array slices do not have any members. They are implicitly convertible to arrays of their underlying type and support index access. Index access is not absolute in the underlying array, but relative to the start of the slice.

Array slices do not have a type name which means no variable can have an array slices as type, they only exist in intermediate expressions.

Note

As of now, array slices are only implemented for calldata arrays.

Array slices are useful to ABI-decode secondary data passed in function parameters:

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

contract Proxy {
    /// Address of the client contract managed by proxy i.e., this contract
    address client;

    constructor(address _client) public {
        client = _client;
    }

    /// Forward call to "setOwner(address)" that is implemented by client
    /// after doing basic validation on the address argument.
    function forward(bytes calldata _payload) external {
        bytes4 sig = abi.decode(_payload[:4], (bytes4));
        if (sig == bytes4(keccak256("setOwner(address)"))) {
            address owner = abi.decode(_payload[4:], (address));
            require(owner != address(0), "Address of owner cannot be zero.");
        }
        (bool status,) = client.delegatecall(_payload);
        require(status, "Forwarded call failed.");
    }
}

Structs

Solidity permet de définir de nouveaux types sous forme de structs, comme le montre l’exemple suivant :

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

// Defines a new type with two fields.
// Declaring a struct outside of a contract allows
// it to be shared by multiple contracts.
// Here, this is not really needed.
struct Funder {
    address addr;
    uint amount;
}

contract CrowdFunding {
    // Structs can also be defined inside contracts, which makes them
    // visible only there and in derived contracts.
    struct Campaign {
        address payable beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID is return variable
        // Creates new struct in memory and copies it to storage.
        // We leave out the mapping type, because it is not valid in memory.
        // If structs are copied (even from storage to storage),
        // types that are not valid outside of storage (ex. mappings and array of mappings)
        // are always omitted, because they cannot be enumerated.
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // Creates a new temporary memory struct, initialised with the given values
        // and copies it over to storage.
        // Note that you can also use Funder(msg.sender, msg.value) to initialise.
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

Le contrat ne fournit pas toutes les fonctionnalités d’un contrat de crowdfunding, mais il contient les concepts de base nécessaires pour comprendre les struct. Les types structs peuvent être utilisés à l’intérieur des mapping et des array et peuvent eux-mêmes contenir des mappages et des tableaux.

Il n’est pas possible pour une structure de contenir un membre de son propre type, bien que la structure elle-même puisse être le type de valeur d’un membre de mappage ou peut contenir un tableau de taille dynamique de son type. Cette restriction est nécessaire, car la taille de la structure doit être finie.

Notez que dans toutes les fonctions, un type structure est affecté à une variable locale avec l’emplacement de données storage. Ceci ne copie pas la structure mais stocke seulement une référence pour que les affectations aux membres de la variable locale écrivent réellement dans l’état.

Bien sûr, vous pouvez aussi accéder directement aux membres de la structure sans l’affecter à une variable locale, comme dans campaigns[campaignID].amount = 0.

Mappages

Vous déclarez le objets de type mapping avec la syntaxe mapping(_KeyType => _ValueType). _KeyType peut être n’importe quel type élémentaire. Cela signifie qu’il peut s’agir de n’importe lequel des types de valeurs intégrés plus les octets et les chaînes de caractères. Les types définis par l’utilisateur ou les types complexes tels que les types de contrat, les énuménations, les mappages, les structs et tout type de tableau, à l’exception des bytes et des string qui ne sont pas autorisés. _ValueType peut être n’importe quel type, y compris les mappages.

Vous pouvez considérer les mappings comme des tables de hashage, qui sont virtuellement initialisées de telle sorte que chaque clé possible existe et est mappée à une valeur dont la représentation binaire est constituée de zéros, de type valeur par défaut. La similitude s’arrête là, les données « clés » ne sont pas stockées dans un mappage, seul son hachage keccak256 est utilisé pour rechercher la valeur.

Pour cette raison, les mappages n’ont pas de longueur ou de concept de clé ou de valeur définie.

Les mappages ne peuvent avoir qu’un emplacement de données en storage et sont donc autorisés pour les variables d’état, comme types référence en storage dans les fonctions ou comme paramètres pour les fonctions de librairies. Ils ne peuvent pas être utilisés comme paramètres ou paramètres de retour de fonctions de contrat publiques.

Vous pouvez marquer les variables de type mapping comme public et Solidity crée un getter pour vous. Le `_KeyType devient un paramètre pour le getter. Si _ValueType est un type de valeur ou une structure, le getter retourne _ValueType. Si _ValueType est un tableau ou un mappage, le getter a un paramètre pour chaque _KeyType, de manière récursive.

In the example below, the MappingExample contract defines a public balances mapping, with the key type an address, and a value type a uint, mapping an Ethereum address to an unsigned integer value. As uint is a value type, the getter returns a value that matches the type, which you can see in the MappingUser contract that returns the value at the specified address.

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

contract MappingExample {
    mapping(address => uint) public balances;

    function update(uint newBalance) public {
        balances[msg.sender] = newBalance;
    }
}

contract MappingUser {
    function f() public returns (uint) {
        MappingExample m = new MappingExample();
        m.update(100);
        return m.balances(address(this));
    }
}

The example below is a simplified version of an ERC20 token. _allowances is an example of a mapping type inside another mapping type. The example below uses _allowances to record the amount someone else is allowed to withdraw from your account.

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

contract MappingExample {

    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowances;

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

    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        approve(sender, msg.sender, amount);
        return true;
    }

    function approve(address owner, address spender, uint256 amount) public returns (bool) {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] -= amount;
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
    }
}

Iterable Mappings

You cannot iterate over mappings, i.e. you cannot enumerate their keys. It is possible, though, to implement a data structure on top of them and iterate over that. For example, the code below implements an IterableMapping library that the User contract then adds data too, and the sum function iterates over to sum all the values.

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

struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }

struct itmap {
    mapping(uint => IndexValue) data;
    KeyFlag[] keys;
    uint size;
}

library IterableMapping {
    function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
        uint keyIndex = self.data[key].keyIndex;
        self.data[key].value = value;
        if (keyIndex > 0)
            return true;
        else {
            keyIndex = self.keys.length;
            self.keys.push();
            self.data[key].keyIndex = keyIndex + 1;
            self.keys[keyIndex].key = key;
            self.size++;
            return false;
        }
    }

    function remove(itmap storage self, uint key) internal returns (bool success) {
        uint keyIndex = self.data[key].keyIndex;
        if (keyIndex == 0)
            return false;
        delete self.data[key];
        self.keys[keyIndex - 1].deleted = true;
        self.size --;
    }

    function contains(itmap storage self, uint key) internal view returns (bool) {
        return self.data[key].keyIndex > 0;
    }

    function iterate_start(itmap storage self) internal view returns (uint keyIndex) {
        return iterate_next(self, uint(-1));
    }

    function iterate_valid(itmap storage self, uint keyIndex) internal view returns (bool) {
        return keyIndex < self.keys.length;
    }

    function iterate_next(itmap storage self, uint keyIndex) internal view returns (uint r_keyIndex) {
        keyIndex++;
        while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
            keyIndex++;
        return keyIndex;
    }

    function iterate_get(itmap storage self, uint keyIndex) internal view returns (uint key, uint value) {
        key = self.keys[keyIndex].key;
        value = self.data[key].value;
    }
}

// How to use it
contract User {
    // Just a struct holding our data.
    itmap data;
    // Apply library functions to the data type.
    using IterableMapping for itmap;

    // Insert something
    function insert(uint k, uint v) public returns (uint size) {
        // This calls IterableMapping.insert(data, k, v)
        data.insert(k, v);
        // We can still access members of the struct,
        // but we should take care not to mess with them.
        return data.size;
    }

    // Computes the sum of all stored data.
    function sum() public view returns (uint s) {
        for (
            uint i = data.iterate_start();
            data.iterate_valid(i);
            i = data.iterate_next(i)
        ) {
            (, uint value) = data.iterate_get(i);
            s += value;
        }
    }
}

Opérateurs impliquant des LValues

Si a est une LValue (c.-à-d. une variable ou quelque chose qui peut être assigné à), les opérateurs suivants sont disponibles en version raccourcie:

``a += e`` équivaut à ``a = a + e``. Les opérateurs ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` et ``^=`` sont définis de la même manière. ``a++`` et ``a--`` sont équivalents à ``a += 1`` / ``a -= 1`` mais l'expression elle-même a toujours la valeur précédente ``a``. Par contraste, ``--a`` et ``++a`` changent également ``a`` de ``1`` , mais retournent la valeur après le changement.

delete

delete a affecte la valeur initiale du type à a. C’est-à-dire que pour les entiers, il est équivalent à a = 0, mais il peut aussi être utilisé sur les tableaux, où il assigne un tableau dynamique de longueur zéro ou un tableau statique de la même longueur avec tous les éléments initialisés à leur valeur par défaut. delete a[x] deletes the item at index x of the array and leaves all other elements and the length of the array untouched. This especially means that it leaves a gap in the array. If you plan to remove items, a mapping is probably a better choice.

Pour les structs, il assigne une structure avec tous les membres réinitialisés. En d’autres termes, la valeur de a après delete a est la même que si a était déclaré sans attribution, avec la réserve suivante :

delete n’a aucun effet sur les mappages (car les clés des mappages peuvent être arbitraires et sont généralement inconnues). Ainsi, si vous supprimez une structure, elle réinitialisera tous les membres qui ne sont pas des mappings et se propagera récursivement dans les membres à moins qu’ils ne soient des mappings. Toutefois, il est possible de supprimer des clés individuelles et ce à quoi elles correspondent : Si a est un mappage, alors delete a[x] supprimera la valeur stockée à x.

Il est important de noter que delete a se comporte vraiment comme une affectation à a, c’est-à-dire qu’il stocke un nouvel objet dans a. Cette distinction est visible lorsque a est une variable par référence : Il ne réinitialisera que a lui-même, et non la valeur à laquelle il se référait précédemment.

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

contract DeleteExample {
    uint data;
    uint[] dataArray;

    function f() public {
        uint x = data;
        delete x; // met x à 0, n' affecte pas data
        delete data; // met data à 0, n'affecte pas x
        uint[] storage y = dataArray;
        delete dataArray; // ceci met dataArray.length à zéro, mais un uint[]
        // est un objet complexe, donc y est affecté est un alias
        // vers l' objet en storage.
        // D' un autre côté: "delete y" est invalid, car l' assignement à
        // une variable locale pointant vers un objet en storage n' est
        // autorisée que depuis un objet en storage.
        assert(y.length == 0);
    }
}

Conversions entre les types élémentaires

Conversions implicites

An implicit type conversion is automatically applied by the compiler in some cases during assignments, when passing arguments to functions and when applying operators. En général, une conversion implicite entre les types valeur est possible si elle a un sens sémantique et qu’aucune information n’est perdue.

Par exemple, uint8 est convertible en uint16 et int128 en int256, mais uint8 n’est pas convertible en uint256 (car uint256 ne peut contenir, par exemple, -1)

Si un opérateur est appliqué à différents types, le compilateur essaie de convertir implicitement l’un des opérandes au type de l’autre (c’est la même chose pour les assignations). This means that operations are always performed in the type of one of the operands.

For more details about which implicit conversions are possible, please consult the sections about the types themselves.

In the example below, y and z, the operands of the addition, do not have the same type, but uint8 can be implicitly converted to uint16 and not vice-versa. Because of that, y is converted to the type of z before the addition is performed in the uint16 type. The resulting type of the expression y + z is uint16`. Because it is assigned to a variable of type ``uint32 another implicit conversion is performed after the addition.

uint8 y;
uint16 z;
uint32 x = y + z;

Conversions explicites

Si le compilateur ne permet pas la conversion implicite mais que vous savez ce que vous faites, une conversion de type explicite est parfois possible. Notez que cela peut vous donner un comportement inattendu et vous permet de contourner certaines fonctions de sécurité du compilateur, donc assurez-vous de tester que le résultat est ce que vous voulez !

Prenons l’exemple suivant où l’on convertit un int8 négatif en un uint :

int  y = -3;
uint x = uint(y);

A la fin de cet extrait de code, x aura la valeur 0xfffffff...fd (64 caractères hexadécimaux), qui est -3 dans la représentation en 256 bits du complément à deux.

Si un entier est explicitement converti en un type plus petit, les bits d’ordre supérieur sont coupés:

uint32 a = 0x12345678;
uint16 b = uint16(a); // b sera désormais 0x5678

Si un entier est explicitement converti en un type plus grand, il est rembourré par la gauche (c’est-à-dire à l’extrémité supérieure de l’ordre). Le résultat de la conversion sera comparé à l’entier original:

uint16 a = 0x1234;
uint32 b = uint32(a); // b will be 0x00001234 now
assert(a == b);

Les types à taille fixe se comportent différemment lors des conversions. Ils peuvent être considérés comme des séquences d’octets individuels et la conversion à un type plus petit coupera la séquence:

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b sera désormais 0x12

Si un type à taille fixe est explicitement converti en un type plus grand, il est rembourré à droite. L’accès à l’octet par un index fixe donnera la même valeur avant et après la conversion (si l’index est toujours dans la plage):

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b sera désormais 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);

Puisque les entiers et les tableaux d’octets de taille fixe se comportent différemment lorsqu’ils sont tronqués ou rembourrés, les conversions explicites entre entiers et tableaux d’octets de taille fixe ne sont autorisées que si les deux ont la même taille. Si vous voulez convertir entre des entiers et des tableaux d’octets de taille fixe de tailles différentes, vous devez utiliser des conversions intermédiaires qui font la troncature et le remplissage désirés. règles explicites:

bytes2 a = 0x1234;
uint32 b = uint16(a); // b sera désormais 0x00001234
uint32 c = uint32(bytes4(a)); // c sera désormais 0x12340000
uint8 d = uint8(uint16(a)); // d sera désormais 0x34
uint8 e = uint8(bytes1(a)); // d sera désormais 0x12

Conversions entre les types littéraux et élémentaires

Types nombres entiers

Les nombres décimaux et hexadécimaux peuvent être implicitement convertis en n’importe quel type entier suffisamment grand pour le représenter sans troncature:

uint8 a = 12; // Bon
uint32 b = 1234; // Bon
uint16 c = 0x123456; // échoue, car devrait tronquer en 0x3456

Tableaux d’octets de taille fixe

Les nombres décimaux ne peuvent pas être implicitement convertis en tableaux d’octets de taille fixe. Les nombres hexadécimaux peuvent être littéraux, mais seulement si le nombre de chiffres hexadécimaux correspond exactement à la taille du type de bytes. Par exception, les nombres décimaux et hexadécimaux ayant une valeur de zéro peuvent être convertis en n’importe quel type à taille fixe:

bytes2 a = 54321; // pas autorisé
bytes2 b = 0x12; // pas autorisé
bytes2 c = 0x123; // pas autorisé
bytes2 d = 0x1234; // bon
bytes2 e = 0x0012; // bon
bytes4 f = 0; // bon
bytes4 g = 0x0; // bon

Les littéraux de chaînes de caractères et les littéraux de chaînes hexadécimales peuvent être implicitement convertis en tableaux d’octets de taille fixe, si leur nombre de caractères correspond à la taille du type bytes:

bytes2 a = hex"1234"; // bon
bytes2 b = "xy"; // bon
bytes2 c = hex"12"; // pas autorisé
bytes2 d = hex"123"; // pas autorisé
bytes2 e = "x"; // pas autorisé
bytes2 f = "xyz"; // débile

Adresses

Comme décrit dans Adresses Littérales, les chaines de caractères hexadécimaux de la bonne taille qui passent le test de somme de contrôle sont de type address. Aucun autre littéral ne peut être implicitement converti au type address.

Les conversions explicites de bytes20 ou de tout type entier en address aboutissent en une address payable`.