Fermer

mai 22, 2018

Compilation et contrats intelligents: ABI expliqué –


La plupart des contrats intelligents sont développés dans un langage de programmation de haut niveau. Le plus populaire actuellement est Solidité avec Vyper espérant prendre le trône dans un futur proche.

Cependant, le mécanisme moteur d'Ethereum ne peut pas comprendre les langages de haut niveau, mais parle à la place dans un langage de niveau inférieur

La machine virtuelle Ethereum (EVM)

Les contrats intelligents Ethereum sont des ensembles d'instructions de programmation exécutées sur tous les nœuds exécutant un client Ethereum complet. La partie d'Ethereum qui exécute les instructions de contrat intelligent est appelée EVM. C'est une machine virtuelle qui ne ressemble pas à la JVM de Java. L'EVM lit une représentation de bas niveau de contrats intelligents appelée Ethereum bytecode .

Le bytecode Ethereum est un langage d'assemblage composé de plusieurs opcodes . Chaque opcode effectue une certaine action sur la blockchain Ethereum

La question est, comment allons-nous de ceci:

 pragma solidity 0.4.24;

contrat Greeter {

    function greet () public constant retourne (chaîne) {
        retour "Bonjour";
    }

}

à ceci:

 PUSH1 0x80 PUSH1 0x40 Mstore PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 Jumpi PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF ET DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0x46 Jumpi JUMPDEST PUSH1 0x0 DUP1 INVERSE JUMPDEST CALLVALUE DUP1 IsZero PUSH2 0x52 Jumpi PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5B PUSH2 0xD6 JUMP JUMPDEST PUSH1 0x40 MOULE DUP1 DUP1 PUSH1 0x20 AJOUTER DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 AJOUTER SWAP2 POP DUP1 MÉLANGER SWAP1 PUSH1 0x20 AJOUTER SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x9B JUMPI DUP1 DUP3 AJOUTER MADAD DUP2 DUP5 AJOUTER MSTORE PUSH1 0x20 DUP2 AJOUTER SWAP1 POP PUSH2 0x80 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 AJOUTER SWAP1 PUSH1 0x1F ET DUP1 ISZERO PUSH2 0xC8 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB PAS ET DUP2 MSTORE PUSH1 0x20 AJOUTER SWAP2 POP JUMPDEST POP SWAP3 POP POP PUSH1 0x40 MLO AD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x40 DUP1 Mcharge SWAP1 dup2 ADD PUSH1 0x40 mStore DUP1 PUSH1 0x5 dup2 mStore PUSH1 0x20 ADD PUSH32 0x48656C6C6F000000000000000000000000000000000000000000000000000000 dup2 mStore POP SWAP1 POP SWAP1 JUMP ARRET LOG1 PUSH6 0x627A7A723058 KECCAK256 SLT 0xec 0xe 0xF5 0xF8 SLT 0xC7 0x2D STATICCALL ADRESSE SHR 0xdb COINBASE 0xb1 ÉQUILIBRE 0xe8 0xf8 DUP14 0xDA 0xad DUP13 LOG1 0x4c 0xb4 0x26 0xc2 DELEGATECALL PUSH7 0x8994D3E002900

Solidity Compiler

Pour l'instant, nous allons nous concentrer sur le compilateur Solidity, mais les mêmes principes s'appliquent à Vyper ou à tout autre langage de haut niveau pour l'EVM.

Commençons par installer Node.js .

Après l'avoir fait, allez sur votre terminal et lancez ceci:

 npm install -g solc

Cela installera solc – le compilateur Solidity. Maintenant, faites un répertoire vide. Dans ce répertoire, créez un fichier appelé SimpleToken.sol et placez le code suivant:

 pragma solidity ^ 0.4.24;

contrat SimpleToken {

    cartographie (address => uint) balances privées;

    constructeur () public {
        _balances [msg.sender] = 1000000;
    }

    function getBalance (compte d'adresse) public constant returns (uint) {
        return _balances [account];
    }

    transfert de fonction (adresse à, montant d'uint) public {
        require (_balances [msg.sender]> = montant);

        _balances [msg.sender] - = montant;
        _balances [to] + = montant;
    }
}

Il s'agit du contrat intelligent de jetons le plus simple, mais il comporte plusieurs fonctionnalités importantes qui seront utiles pour ce tutoriel. Ils sont:

  • fonctions publiques
  • fonctions privées
  • propriétés

Après avoir fait cela, exécutez le solc nouvellement installé sur votre fichier. Pour ce faire, exécutez ce qui suit:

 solcjs SimpleToken.sol

Vous devriez obtenir une sortie similaire à celle-ci:

 L'option invalide sélectionnée doit spécifier soit --bin ou --abi

Et votre compilation devrait échouer.

Qu'est-ce qui vient de se passer? Qu'est-ce que bin et qu'est-ce que abi ?

bin est simplement une représentation binaire compacte du bytecode compilé. Les opcodes ne sont pas référencés par PUSH PULL ou DELEGATECALL mais leurs représentations binaires, qui ressemblent à des nombres aléatoires lorsqu'elles sont lues par un éditeur de texte. [19659030] ABI – Application Binary Interface

Une fois notre sortie bin déployée dans la blockchain, le contrat recevra son adresse et le bytecode sera poussé dans le stockage Ethereum. Mais un gros problème subsiste: comment interpréter le code?

Il n'y a pas moyen de savoir, à partir de bytecode seulement, que le contrat a des fonctions transfer (:) et getBalance (:) . Il est encore moins clair si ces fonctions sont publiques privées ou constantes . Le contrat est déployé sans contexte .

Appeler un tel contrat serait presque impossible. Nous ne savons pas où chaque fonction se trouve dans le bytecode, quels paramètres il prend, ou si nous serons autorisés à l'appeler du tout. C'est là que l'ABI entre en jeu.

L'ABI est un fichier .json qui décrit le contrat déployé et ses fonctions. Cela nous permet de contextualiser le contrat et d'appeler ses fonctions.

Essayons une fois de plus d'exécuter nos solcjs . Exécutez les commandes suivantes:

 solcjs SimpleToken.sol --abi
solcjs SimpleToken.sol --bin

Votre répertoire devrait maintenant avoir une structure comme celle-ci:

.
Simple── SimpleToken.sol
Simple── SimpleToken_sol_SimpleToken.abi
Simple── SimpleToken_sol_SimpleToken.bin

Le fichier SimpleToken_sol_SimpleToken.abi devrait ressembler à ceci:

[{
    "constant": false,
    "inputs": [{
        "name": "to",
        "type": "address"
    }, {
        "name": "amount",
        "type": "uint256"
    }],
    "nom": "transfert",
    "sorties": [],
    "payable": faux,
    "stateMutability": "non remboursable",
    "type": "fonction"
}, {
    "constant": vrai,
    "entrées": [{
        "name": "account",
        "type": "address"
    }],
    "name": "getBalance",
    "sorties": [{
        "name": "",
        "type": "uint256"
    }],
    "payable": faux,
    "stateMutability": "vue",
    "type": "fonction"
}, {
    "entrées": [],
    "payable": faux,
    "stateMutability": "non remboursable",
    "type": "constructeur"
}]

On voit que le fichier décrit les fonctions du contrat. Il définit:

  • leur nom: le nom des fonctions
  • leur payabilité: si vous pouvez leur envoyer de l'éther
  • les sorties: la (les) valeur (s) de retour de la fonction
  • leur mutabilité d'état: la fonction est en lecture seule ou a un accès en écriture.

Tout cela est raisonnablement facile à comprendre à la lecture. Mais plus tôt, j'ai mentionné que l'ABI définit également comment l'utilisateur peut appeler les fonctions – c'est-à-dire l'emplacement de la fonction par rapport à l'adresse du contrat intelligent.

Connaissant le nom de la fonction n'est pas assez; nous devons aussi savoir comment (où) l'appeler

Ceci est fait en exécutant un algorithme déterministe sur les propriétés de la fonction dont nous avons parlé plus haut (le nom, la payabilité, les sorties etc.). Les détails de cette fonction peuvent être trouvés ici .

Exemple

L'ABI est la description de l'interface contractuelle. Il ne contient aucun code et ne peut pas être exécuté seul. Le bytecode est le code EVM exécutable, mais en lui-même il est sans contexte.

Pour appeler des fonctions dans des contrats intelligents, nous devons utiliser à la fois l'ABI et le bytecode. Heureusement pour nous, tout cela est abstrait lorsque nous interagissons avec des contrats intelligents en utilisant l'un des frameworks fournis.

Un exemple utilisant le framework web3.js ressemblerait à ceci:

 var simpletokenContract = web3.eth.contract ([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}]"nom": "transfert", "sorties": []"payable": false, "stateMutability": "nonpayable", "type": "function" }, {"constant": true, "entrées": [{"name":"account","type":"address"}]"nom": "getBalance", "sorties": [{"name":"","type":"uint256"}]"payable": false, "stateMutability": "voir", "type" : "function"}, {"inputs": []"payable": false, "stateMutability": "nonpayable", "type": "constructeur"}]);
var simpletoken = simpletokenContract.new (
   {
     de: web3.eth.accounts [0],
     données: « 0x608060405234801561001057600080fd5b50620f42406000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610252806100666000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a9059cbb14610051578063f8b2cb4f1461009e575b600080fd5b34801561005d57600080fd5b5061009c600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506100f5565b005b3480156100aa57600080fd5b506100df600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101de565b6040518082815260200191505060405180910390f35b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561014257600080fd5b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925 05081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509190505600a165627a7a72305820c9da07d4976adbf00a4b5fe4e23330dbaf3cdcbfd4745eed78c702bf27d944060029" ,
     gaz: '4700000'
   }, fonction (e, contrat) {
    console.log (e, contrat);
    if (typeof contract.address! == 'non défini') {
         console.log ('Contract mined! address:' + contract.address + 'transactionHash:' + contrat.transactionHash);
    }
 })

Nous avons d'abord défini le simpelTokenContract qui est une description de l'apparence du contrat de l'extérieur. Nous l'avons fait en lui passant l'ABI du SimpleToken.sol .

Puis nous avons créé une instance du contrat simpletoken en appelant le simpletokenContract.new (...) et en lui transmettant les données du contrat (le code exécutable).

web3.js a combiné les deux en arrière-plan et a maintenant tout le nécessaire

Conclusion

Dans ce bref aperçu de la compilation de contrats intelligents, nous avons expliqué ABI et comment les contrats intelligents déployés sur la chaîne de blocs Ethereum peuvent être invoqués. Bien que vous n'ayez jamais à l'utiliser directement, il vaut la peine d'en être conscient, car trop d'abstraction peut mener à des bogues.