Développer avec Solidity

Solidity est le langage de développement des contrats intelligents déployés sur la blockchain Ethereum.

Documentation

La documentation officielle de solidity est disponible ici :


Unités et Variables globales
https://docs.soliditylang.org/en/v0.8.3/units-and-global-variables.html

https://github.com/ProjectOpenSea/opensea-creatures/

https://next-stack.github.io/tutorials/

https://ethereum-waffle.readthedocs.io/en/latest/getting-started.html
https://docs.openzeppelin.com/contracts/3.x/api/token/erc1155
https://wizard.openzeppelin.com/#erc1155

https://defi-learning.org/
https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity

Security Considerations & Testing

  - https://www.pragmaticcoders.com/blog/solidity-smart-contract
  - Known attacks : https://consensys.github.io/smart-contract-best-practices/known_attacks/
  - https://securityboulevard.com/2020/05/solidity-top-10-common-issues/
  - https://medium.com/coinmonks/common-attacks-in-solidity-and-how-to-defend-against-them-9bc3994c7c18
  - https://ethernaut.openzeppelin.com/level/0x4dF32584890A0026e56f7535d0f2C6486753624f => after coinflip
  - ethernaut solutions : https://github.com/STYJ/Ethernaut-Solutions

Outils

Il existe plusieurs IDE pour Solidity et libre à chacun de choisir 
https://theblockchainguy.dev/hardhat-vs-truffle-vs-remix
Elles s'intègrent plus ou moins avec les autres outils de test par exemple.

Compilateur

Le version du compilateur est importante. Le langage est fréquemment mis à jour et évolue. Par ailleurs, la version a un impact sur le gaz des opérations.

HardHat

Hardhat : https://hardhat.org/getting-started/
npm install --save-dev hardhat
npx hardhat

Compiler les contrats
npx hardhat compile

To run Hardhat Network in this way, run npx hardhat node:

$ npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
This will expose a JSON-RPC interface to Hardhat Network. To use it connect your wallet or application to http://127.0.0.1:8545.

If you want to connect Hardhat to this node, for example to run a deployment script against it, you simply need to run it using --network localhost.

To try this, start a node with npx hardhat node and re-run the deployment script using the network option:

TypeScript
JavaScript
npx hardhat run scripts/deploy.ts --network localhost
Congrats! You have created a project and compiled, tested and deployed a smart contract.

Remix

interface en ligne Remix IDE : https://remix.ethereum.org/

Pour sauvegarder vos fichiers en local, il faut utiliser le démon remixd
remixd -s C:\Users\joan\source\repos\Solidity -u https://remix.ethereum.org

Ganache

https://github.com/trufflesuite/ganache/releases/tag/v7.0.0
https://trufflesuite.com/ganache/

Ganache permet l'execution rapide d'une chaine de block ethereum personelle disponible pour des tests commandes et inspection.

Waffle

Tests
https://getwaffle.io/
https://ethereum-waffle.readthedocs.io/en/latest/getting-started.html


Commentaires

//

/**
*
*/
natspec
/// @title (titre) and @author (auteur) sont plutôt évidents.

/// @notice explique à un utilisateur ce que le contrat / fonction fait. @dev est pour donner plus de détails aux développeurs.

@param et @return servent à décrire chaque paramètres et ce que la fonction renvoie.

Vous n'avez pas tout le temps besoin d'utiliser tous ces tags pour chaque fonction — tous les tags sont optionnels. Au minimum, laissez une note @dev pour expliquer ce que chaque fonction fait.


Fonctions

Interne et externe
En plus de public et private, Solidity a deux autres visibilité pour les fonctions : internal (interne) et external (externe).
internal est similaire à private, à l'exception qu'elle est aussi accessible aux contrats qui héritent de ce contrat. (On dirait que c'est ce que l'on veut!).

external est similaire à public, à l'exception que ces fonctions peuvent SEULEMENT être appelées à l'extérieur du contrat - elles ne peuvent pas être appelées par d'autres fonctions à l'intérieur du contrat. Nous expliquerons plus tard pourquoi utiliser external plutôt que public.

  function _isReady(Zombie storage _zombie) internal view returns (bool) {
      return (_zombie.readyTime <= now);
  }
en Solidity une fonction peut retourner plus d'une valeur. exemple que nous avons vu qui retourne plusieurs valeurs. Nous allons voir comment gérer cela :

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // C'est comme ça que vous faites une affectation multiple :
  (a, b, c) = multipleReturns();
}

// Ou si nous voulons seulement une des valeurs ci dessus :
function getLastReturnValue() external {
  uint c;
  // Nous pouvons laisser les autres champs vides :
  (,,c) = multipleReturns();
}

Modificateurs 

Il existent des modificateurs de visibilité qui contrôlent quand et depuis où la fonction peut être appelée : 
  • private veut dire que la fonction ne peut être appelée que par les autres fonctions à l'intérieur du contrat;
  • internal est comme private mais en plus, elle peut être appelée par les contrats qui héritent de celui-ci; avec external, la fonction ne peut être appelée que depuis l'extérieur du contrat;
  • public, elle peut être appelée depuis n'importe où, à l'intérieur et à l'extérieur.

modificateurs d'état


Il existent aussi des modificateurs d'état, qui nous indiquent comment la fonction interagie avec la BlockChain : 
  • view nous indique qu'en exécutant cette fonction, aucune donnée ne saura sauvegardée/modifiée. 
  • pure nous indique que non seulement aucune donnée ne saura sauvée sur la BlockChain, mais qu'en plus aucune donnée de la BlockChain ne sera lue. 
Ces 2 fonctions ne coûtent pas de gas si elles sont appelées depuis l'extérieur du contrat (mais elle coûtent du gas si elles sont appelées intérieurement par une autre fonction).

modificateur payable

Une des choses qui rend Solidity et Ethereum vraiment cool est le modificateur payable, une fonction payable est une fonction spéciale qui peut recevoir des Ether.

modificateurs personnalisés

Ensuite nous avons les modificateurs personnalisés : onlyOwner et aboveLevel par exemple. Nous avons pu déterminer des logiques personnalisés pour ceux-ci, afin de choisir de quelles manières ils affectent une fonction.

Modificateurs de fonction 
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

Modificateurs de fonction  avec argument
modifier olderThan(uint _age, uint _userId) {
  require (age[_userId] >= _age);
  _;
}

Boucles for

La syntaxe des boucles for en Solidity est similaire qu'en JavaScript.

Voici un exemple où nous voulons faire un tableau de nombre pairs :

function getEvens() pure external returns(uint[]) {
  uint[] memory evens = new uint[](5);
  // On suit l'index du nouveau tableau
  uint counter = 0;
  // On itère de 1 à 19 avec une boucle `for` :
  for (uint i = 1; i <= 10; i++) {
    // Si `i` est pair...
    if (i % 2 == 0) {
      // On l'ajoute au tableau
      evens[counter] = i;
      // On incrémente le `counter` de 1 :
      counter++;
    }
  }
  return evens;
}

Informations Utiles

Différence entre msg.sender et tx.origin

tx.origin fait référence au compte externe à l'origine de la transaction, alors que msg.sender fait référence au compte immédiat qui est en train de réaliser la transaction.

Les valeurs peuvent être identiques si un compte externe est à l'origine immédiate de la transaction, mais elles seront différentes si un contrat intelligent est positionné dans la transaction.

Si Bob fait appel à un contrat intelligent pour déclencher une transaction, alors tx.origin fera référence au compte de Bob, alors que msg.sender aura pour valeur l'adresse du contrat intelligent qui la poursuit.

Assert vs Require

assert est la même chose que require, et va renvoyer une erreur si ce n'est pas vérifié. La différence entre assert et require c'est que require va rembourser l'utilisateur du gas restant quand la fonction échoue, alors que assert non. La plupart du temps vous allez vouloir utiliser require dans votre code, assert est plutôt utilisé quand quelque chose a vraiment mal tourné avec le code (comme un débordement d'uint).

Conversion de données

https://docs.soliditylang.org/en/v0.8.3/types.html#conversions-between-elementary-types

Il est possible de convertir un type de données vers un autre. Cependant, dans le cas d'une conversion d'un type plus grand vers un type plus petit, une corruption de données à prévoir.

Exemple avec une représentation en bytes d'un nombre :

bytes32 bigNumber = 0x1111111111111111111111111111111111111111111111111111111111111111;

si l'on convertit ce nombre vers le type bytes4, on obtient :
bytes4 smallNumber = 0x11111111;
Les bits les plus élévés sont ignorés (ceux de gauche).

si à nouveau on effectue une conversion, mais ce coup ci vers un type plus grand :

bytes newBigNumber = 0x0000000000000000000000000000000000000000000000000000000011111111;
Les bits les plus élevés ("à gauche") sont complétés avec des 0.

Gas

Sur Ethereum, réaliser des opérations à un cout, pour encourager les mineurs à valider les blocs et prévenir une utilisation abusive du réseau. Ce cout, appelé gas cost, dépend du prix du gas et du cout de l'opération à réaliser : gas * gas price.
C'est l'émetteur d'une transaction qui va supporter ce coût. Si l'émetteur ne fournit pas assez de gaz, la transaction peut échouer. Le gaz fournit va également influencer les mineurs, plus le gas fourni sera élevé, plus les mineurs seront enclin à inclure une transaction dans le calcul d'un bloc.
Certaines opérations sont plus couteuses que d'autres, la création d'un contrat est plus couteuse qu'un simple envoi d'ether. La sauvegarde de données dans la blockchain est également plus couteuse que la simple lecture de données. En terme de lecture, on note également que la consultation d'une constante est plus économique que celle d'une variable.

Le cout des opérations arithmétiques de base sont détaillées dans le Livre Jaune d'Ethereum.
Une des opérations les plus coûteuse en Solidity est d'utiliser storage - particulièrement quand on écrit.

Dans la plupart des langages de programmation, faire une boucle sur un grand ensemble de données est coûteux. Mais en Solidity, c'est beaucoup moins cher que d'utiliser storage s'il y a une fonction external view, puisque view ne coûte aucun gas. (Et les gas coûte réellement de l'argent pour vos utilisateurs !).


ABI du contrat

L'autre chose que Web3.js à besoin pour communiquer avec votre contrat et son ABI.

ABI veut dire "Application Binary Interface" (Interface Binaire d'Application). Fondamentalement, c'est une représentation des fonctions de votre contrat au format JSON qui indique à Web3.js comment formater les appels aux fonctions pour que votre contrat les comprenne.

Tutoriaux et Challenges

OpenZeppelin

https://github.com/OpenZeppelin/openzeppelin-contracts
Acces Control

https://docs.openzeppelin.com/contracts/4.x/access-control

Ethernaut

Une série d'exercices pour s'entrainer sur les erreurs souvent commises lors du développement d'un contrat intelligent :

https://ethernaut.openzeppelin.com/

CryptoZombies

https://cryptozombies.io/

Aide et solutions

  • https://cmichel.io/ethernaut-solutions/
  • https://forum.openzeppelin.com/t/ethernaut-community-solutions/561
Web3
https://web3js.readthedocs.io/
https://github.com/ChainSafe/web3.js
https://docs.ipfs.io/how-to/best-practices-for-nft-data/

NFTs
https://dev.to/abdulmaajid/how-to-create-an-nfterc-721-using-openzeppelin-3778
Storage
https://ipfs.io/
https://docs.ipfs.io/how-to/mint-nfts-with-ipfs/#how-minty-works
https://nft.storage/#getting-started
https://github.com/ProjectOpenSea/opensea-creatures/

Commentaires

Posts les plus consultés de ce blog

Sécurité des Applications

Principes de la Programmation Orientée Objet

Principe de Responsabilité Unique