WEB3DEV

Cover image for Compilação e Contratos Inteligentes: ABI Explicada
Rafael Ojeda
Rafael Ojeda

Posted on • Atualizado em

Compilação e Contratos Inteligentes: ABI Explicada

Sumário
  1. Compilação e Contratos Inteligentes: ABI Explicada
  2. A Máquina Virtual Ethereum (EVM)
  3. Compilador de Solidity
  4. ABI - Interface Binária de Aplicação
  5. Exemplo
  6. Conclusão

Compilação e Contratos Inteligentes: ABI Explicada

Blockchain Ethereum

A maioria dos contratos inteligentes são desenvolvidos em uma linguagem de programação de alto nível. A mais popular atualmente é a Solidity, com Vyper esperando tomar o trono em um futuro próximo.

Entretanto, o mecanismo que impulsiona o Ethereum não consegue entender as linguagens de alto nível, mas em vez disso fala em uma linguagem de nível muito inferior.

A Máquina Virtual Ethereum (EVM)

Os contratos inteligentes Ethereum são conjuntos de instruções de programação sendo executados em todos os nós que executam um cliente Ethereum completo. A parte do Ethereum que executa as instruções do contrato inteligente é chamada de EVM. É uma máquina virtual não muito diferente da JVM de Java. O EVM lê uma representação de baixo nível de contratos inteligentes chamada de Ethereum bytecode.

O Ethereum bytecode é uma linguagem de montagem composta de múltiplos opcodes. Cada opcode executa uma certa ação sobre a blockchain Ethereum.

A questão é, como vamos a partir disto:

pragma solidity 0.4.24;

contract Greeter {

    function greet() public constant returns (string) {
        return "Hello";
    }

}
Enter fullscreen mode Exit fullscreen mode

a isto:

PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x41 JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV PUSH4 0xFFFFFFFF AND DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0x46 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x52 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5B PUSH2 0xD6 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x9B JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x80 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0xC8 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x60 PUSH1 0x40 DUP1 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x5 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x48656C6C6F000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP SWAP1 POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLT 0xec 0xe 0xf5 0xf8 SLT 0xc7 0x2d STATICCALL ADDRESS SHR 0xdb COINBASE 0xb1 BALANCE 0xe8 0xf8 DUP14 0xda 0xad DUP13 LOG1 0x4c 0xb4 0x26 0xc2 DELEGATECALL PUSH7 0x8994D3E002900

Enter fullscreen mode Exit fullscreen mode

Compilador de solidity

Por enquanto, vamos nos concentrar no compilador de Solidity, mas os mesmos princípios se aplicam ao Vyper ou a qualquer outra linguagem de alto nível para o EVM.

Primeiramente: instale o Node.js.

Depois de ter feito isso, vá até seu terminal e execute isto:

 npm install -g solc
Enter fullscreen mode Exit fullscreen mode

Isto instalará o solc - o compilador de solidity. Agora faça um diretório vazio. Nesse diretório, crie um arquivo chamado SimpleToken.sol e coloque o seguinte código:

pragma solidity ^0.4.24;

contract SimpleToken {

    mapping(address => uint) private _balances;

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

    function getBalance(address account) public constant returns (uint) {
        return _balances[account];
    }

    function transfer(address to, uint amount) public {
        require(_balances[msg.sender] >= amount);

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

Enter fullscreen mode Exit fullscreen mode

Este é o contrato inteligente simbólico mais simples, mas tem várias características importantes que serão úteis para este tutorial. Eles são:

  • funções públicas
  • funções privadas
  • propriedades

Depois de ter feito isso, execute o solc recém-instalado em seu arquivo. Você faz isso executando o seguinte:

solcjs SimpleToken.sol
Enter fullscreen mode Exit fullscreen mode

Você deve obter uma saída semelhante a esta:

invalid option selected, must specify either  - -bin or - -abi
Enter fullscreen mode Exit fullscreen mode

E sua compilação deve falhar.

O que acabou de acontecer? O que é bin e o que é abi ?

bin é simplesmente uma representação binária compacta do bytecode compilado. Os opcodes não são referenciados por PUSH, PULL ou DELEGATECALL, mas suas representações binárias, que se parecem com números aleatórios quando lidos por um editor de texto.

ABI - Interface Binária de Aplicação

Uma vez que nossa bin for enviada para a blockchain, o contrato terá seu endereço e o bytecode será empurrado para o armazenamento do Ethereum. Mas um grande problema permanece: como interpretamos o código?

Não há como saber, apenas pelo bytecode, que o contrato tem funções de transfer(:) e getBalance(:). É ainda menos claro se estas funções são public, private ou constant. O contrato é implantado sem contexto.

Chamar um contrato desse tipo seria quase impossível. Não sabemos onde cada função está no bytecode, quais parâmetros são necessários, ou se nos será permitido chamá-la de alguma forma. É aqui que a ABI entra em jogo.

A ABI é um arquivo .json que descreve o contrato implantado e suas funções. Ele nos permite contextualizar o contrato e chamar suas funções.

Vamos tentar executar nossos solcjs mais uma vez. Execute os seguintes comandos:

solcjs SimpleToken.sol --abi
solcjs SimpleToken.sol --bin
Enter fullscreen mode Exit fullscreen mode

Seu diretório deve agora ter uma estrutura como esta:

.

├── SimpleToken.sol
├── SimpleToken_sol_SimpleToken.abi
└── SimpleToken_sol_SimpleToken.bin
Enter fullscreen mode Exit fullscreen mode

O arquivo SimpleToken_sol_SimpleToken.abi deve ser parecido com este:

  [{
    "constant": false,
    "inputs": [{
        "name": "to",
        "type": "address"
    }, {
        "name": "amount",
        "type": "uint256"
    }],
    "name": "transfer",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}, {
    "constant": true,
    "inputs": [{
        "name": "account",
        "type": "address"
    }],
    "name": "getBalance",
    "outputs": [{
        "name": "",
        "type": "uint256"
    }],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
}, {
    "inputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "constructor"
}]

Enter fullscreen mode Exit fullscreen mode

Podemos ver que o arquivo descreve as funções do contrato. Ele define:

  • seu nome: o nome das funções
  • sua capacidade de pagamento: se você pode enviar ether para eles
  • as saídas: o(s) valor(es) de retorno da função
  • sua mutabilidade estatal: se a função é somente de leitura ou tem acesso à escrita.

Tudo isso é razoavelmente fácil de entender a partir da sua leitura. Mas mencionei anteriormente que a ABI também define como o usuário pode chamar as funções - ou seja, a localização da função em relação ao endereço do contrato inteligente.

Conhecer o nome da função não é suficiente; também precisamos saber como (onde) chamá-la.

Isto é feito executando um algoritmo determinístico sobre as propriedades da função que mencionamos anteriormente (o nome, a pagabilidade, as saídas, etc.). Os detalhes desta função podem ser encontrados aqui.

Exemplo

A ABI é a descrição da interface do contrato. Ela não contém código e não pode ser executada por si só. O bytecode é o código EVM executável, mas por si só é sem contexto.

Para chamar funções em contratos inteligentes, precisamos usar tanto a ABI quanto o bytecode. Felizmente para nós, tudo isso é abstraído quando estamos interagindo com contratos inteligentes utilizando uma das estruturas fornecidas.

Um exemplo usando a estrutura do web3.js seria semelhante a este:

var simpletokenContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
var simpletoken = simpletokenContract.new(
   {
     from: web3.eth.accounts[0],
     data: "0x608060405234801561001057600080fd5b50620f42406000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610252806100666000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a9059cbb14610051578063f8b2cb4f1461009e575b600080fd5b34801561005d57600080fd5b5061009c600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506100f5565b005b3480156100aa57600080fd5b506100df600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506101de565b6040518082815260200191505060405180910390f35b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561014257600080fd5b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490509190505600a165627a7a72305820c9da07d4976adbf00a4b5fe4e23330dbaf3cdcbfd4745eed78c702bf27d944060029",
     gas: '4700000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })

Enter fullscreen mode Exit fullscreen mode

Primeiro definimos o simpelTokenContract, que é uma descrição de como o contrato é visto de fora. Fizemos isso passando a ABI do SimpleToken.sol.

Depois criamos uma instância do contrato simpletoken chamando o simpletokenContract.new(...) e passando para ela os dados do contrato (o código executável).

web3.js combinou os dois em segundo plano e agora tem todas as informações necessárias para chamar as funções em nosso contrato inteligente.

Conclusão

Nesta breve visão geral da compilação de contratos inteligentes, explicamos a ABI e como os contratos inteligentes implantados na blockchain Ethereum podem ser invocados. Embora você nunca terá que usar isso diretamente, vale a pena estar ciente disso, pois muita abstração pode levar a bugs.

Compartilhe este artigo FACEBOOK REDDIT TWITTER LINKEDIN

Artigo escrito por Mislav Javor e traduzido para o português por Rafael Ojeda

Você pode ler o artigo original em inglês aqui


Abrace a oportunidade de elevar sua jornada de desenvolvimento para um nível superior. A compilação de Contratos é apenas o começo; os builds incríveis da WEB3DEV representam a chave de entrada para o emocionante cenário web3. 🚀🧑‍💻

Não perca tempo, 👉inscreva-se👈 agora mesmo e comece a desbravar o universo Blockchain!
 
Seja também WEB3DEV!

Latest comments (0)