9  Prática sobre Ferramentas de Desenvolvimento e Frameworks Ethereum: Introdução ao Web3

Introdução ao Web3

Resumo
Nesta prática são apresentadas algumas ferramentas de Desenvolvimento e Frameworks para o desenvolvimento e implantação de Contratos Inteligentes. Apresenta uma introdução ao web3, métodos de desenvolvimento e implantação de contratos inteligentes pelo console javascript do cliente Geth e via Remix IDE. Conectamos na Rede Ethereum Privada Local e na Rede Simulada do Ganache via Metamask.

9.1 Introdução

A proposta é explorarmos a biblioteca web3 com o cliente Geth, e os métodos de desenvolvimento, implantação e verificação de contratos inteligentes. Testaremos comandos da API RPC e faremos o deploy de contratos inteligentes utilizando o console Javascript, utilizando comandos da API RPC e a IDE Remix.

A web3 é uma biblioteca JavaScript que pode ser usada na comunicação com um Nó Ethereum expondo métodos que podem ser acessados via RPC.

A interação com a instância de execução do cliente Geth pode ser feita via console Javascript do Geth com a parâmetro attach. Vários métodos de consulta e gerenciamento do blockchain são expostos e comandos podem ser executados neste console.

Uma outra forma de se comunicar com o cliente geth é via RPC. Já vimos muitos comandos providos pela web3, por exemplo, eth.accounts que retorna a lista de contas locais.

9.2 Ambiente de Execução para a Rede Local

Para essa prática iremos utilizar os comandos definidos na Aula 05: Criando uma Rede Ethereum Privada: Ethash.

Minha instalação do cliente geth está em ~/go-ethereum-1.11.6/build/bin/ então lembre-se de colocar o prefixo antes de cada um dos comandos.

  1. Iniciar clef em um terminal. O comando para o console do clef pode ser visto no Código 9.I.
Listing 9.I: Comando do Terminal – clef
[.etherprivate-ethash]$ $HOME/go-ethereum-1.11.6/build/bin/clef --chainid 786 --keystore $HOME/.etherprivate-ethash/keystore --configdir $HOME/.etherprivate-ethash/clef --http
  1. Iniciar o cliente de execução geth com suporte ao web3. O comando para o console do geth pode ser visto no Código 9.II.
Listing 9.II: Comando do Terminal – Geth
[.etherprivate-ethash]$ $HOME/go-ethereum-1.11.6/build/bin/geth --networkid 786 --datadir ~/.etherprivate-ethash/ --syncmode full --allow-insecure-unlock  --identity "RAGEtherPrivate"  --http --http.addr 127.0.0.1 --http.port 8559 --http.api "eth,net,web3,personal,engine,admin,debug,miner,txpool" --keystore ~/.etherprivate-ethash/keystore --authrpc.addr localhost --authrpc.port 8551 --authrpc.vhosts localhost --authrpc.jwtsecret ~/.etherprivate-ethash/geth/jwtsecret --nodiscover --maxpeers 15 --miner.etherbase=0x2db017e44b03b37755a4b15e14cd799f83de4c13 --signer=$HOME/.etherprivate-ethash/clef/clef.ipc
  1. O comando para o console do prysm: Não precisa! Estamos usando a versão 1.11.6-stable-ea9e62ca que ainda suporta o ethash, algoritmo de consenso Proof-of-Work (PoW).

  2. Iniciar um console JavaScript para a interação com a instância de execução do geth conforme o Código 9.III.

Listing 9.III: Comando do Terminal – Console Javascript
[.etherprivate-ethash]$ $HOME/go-ethereum-1.11.6/build/bin/geth attach $HOME/.etherprivate-ethash/geth.ipc
Welcome to the Geth JavaScript console!

instance: Geth/RAGEtherPrivate/v1.11.6-stable-ea9e62ca/linux-amd64/go1.21.1
coinbase: 0x2db017e44b03b37755a4b15e14cd799f83de4c13
at block: 0 (Wed Dec 31 1969 21:00:00 GMT-0300 (-03))
 datadir: /home/rogerio/.etherprivate-ethash
 modules: admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 rpc:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d or type exit
> 

A Figura 9.I apresenta os comandos executando em cada um dos terminais.

Figure 9.I: Terminais

9.3 Explorando Web3 com Geth

Utilize o console Javascript para verificar se os recursos web3 estão disponíveis, digite no console web3. e dê um tab. Escolhendo o comando web3.version como apresentado no Código 9.IV serão apresentadas algumas informações.

Listing 9.IV: Console Javascript
> web3.
web3.BigNumber         web3.miner             
web3._extend           web3.net               
web3._requestManager   web3.padLeft           
web3.admin             web3.padRight          
web3.bzz               web3.personal          
web3.constructor       web3.providers         
web3.createBatch       web3.reset             
web3.currentProvider   web3.rpc               
web3.db                web3.setProvider       
web3.debug             web3.settings          
web3.eth               web3.sha3              
web3.ethash            web3.shh               
web3.fromAscii         web3.toAscii           
web3.fromDecimal       web3.toBigNumber       
web3.fromICAP          web3.toChecksumAddress 
web3.fromUtf8          web3.toDecimal         
web3.fromWei           web3.toHex             
web3.isAddress         web3.toUtf8            
web3.isChecksumAddress web3.toWei             
web3.isConnected       web3.txpool            
web3.isIBAN            web3.version           
> web3.version
{
  api: "0.20.1",
  ethereum: undefined,
  network: "786",
  node: "Geth/RAGEtherPrivate/v1.11.6-stable-ea9e62ca/linux-amd64/go1.21.1",
  whisper: undefined,
  getEthereum: function(callback),
  getNetwork: function(callback),
  getNode: function(callback),
  getWhisper: function(callback)
}
> 

Os recursos da biblioteca do Ethereum (eth) que estão disponíveis podem ser verificados no console conforme o Código 9.V.

Listing 9.V: Console Javascript
> web3.eth.
..
web3.eth.accounts                   
web3.eth.blockNumber                
web3.eth.chainId                    
web3.eth.coinbase                   
...
web3.eth.gasPrice                   
web3.eth.getAccounts                
web3.eth.getBalance                 
web3.eth.getBlock                   
web3.eth.getBlockByHash             
web3.eth.getBlockByNumber           
web3.eth.getBlockNumber             
web3.eth.getCoinbase                
web3.eth.getGasPrice                
...
web3.eth.getTransaction             
web3.eth.getTransactionReceipt      
web3.eth.submitTransaction          
web3.eth.syncing                    
> web3.eth.accounts
["0x2db017e44b03b37755a4b15e14cd799f83de4c13", "0x7a7686ad451d2865a2246e239b674aefd4c6c27c", "0x1bba02873cc1c11f369a7b692f5f3de8ff7bbe80", "0x1170bbdc51d3791be6ba31dd6ed04383c146c2ed"]
> web3.eth.chainId
function()
> web3.eth.chainId()
"0x312"
> eval(0x312)
786
>

No exemplo do Código 9.V foram recuperadas as contas com web3.eth.accounts e o id da rede em execução com a função web3.eth.chainId().

9.4 Deploy do Contrato via Console Javascript

Faremos o deploy de um contrato usando o console JavaScript. O passo a passo pode ser visto no livro texto e iremos reproduzir aqui a sequência:

  1. Executar o cliente de execução geth e clef.
  2. Compilar o código do contrato.
  3. Criar um script de deployment, usando a ABI e o bytecode, e algum código JavaScript.
  4. Faremos o deploy do contrato via linha de comando pelo console JavaScript.
  5. Interagir com o contrato via um frontend web.

9.4.1 Web3 deployment: Executar o cliente geth e clef

  • Executar o clef. []
  • Executar o geth. []
  • Executar o console JavaScript. []

Executamos esse passo na Seção 9.2.

9.4.2 Web3 deployment: Compilar o código do contrato

O exemplo de contrato que iremos compilar e fazer o deploy é o valueChecker o Código 9.VI.

Listing 9.VI: Contrato valueChecker
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity ^0.8.19;
contract valueChecker {
    uint price = 10;
    event valueEvent(bool returnValue);
    function Matcher (uint8 x) public returns (bool) {
        if (x>=price) {
            emit valueEvent(true);
            return true;
        }
    }
}

Compile o contrato com o solc ou utilizando o Remix IDE, gerando o binário e a ABI, conforme apresentado no Código 9.VII.

Listing 9.VII: Gerando o bytecode e a ABI
$ solc --bin --abi -o bin ValueChecker.sol
$ ls
bin  deploy.js  ValueChecker.sol
$ cd bin
$ ls
valueChecker.abi  valueChecker.bin
$ cat valueChecker.bin
6080604052600a60005534801561001557600080fd5b5061018b806100256000396000f3fe60806040523480
1561001057600080fd5b506004361061002b5760003560e01c8063f9d55e2114610030575b600080fd5b6100
4a600480360381019061004591906100f2565b610060565b604051610057919061013a565b60405180910390
f35b600080548260ff16106100ae577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8
b4b6239c600160405161009d919061013a565b60405180910390a1600190506100af565b5b919050565b6000
80fd5b600060ff82169050919050565b6100cf816100b9565b81146100da57600080fd5b50565b6000813590
506100ec816100c6565b92915050565b600060208284031215610108576101076100b4565b5b600061011684
8285016100dd565b91505092915050565b60008115159050919050565b6101348161011f565b82525050565b
600060208201905061014f600083018461012b565b9291505056fea264697066735822122088a7e63726327b
857c0d0a6d073976f05d5073826c629671c857a375db35d51c64736f6c63430008110033

$ cat valueChecker.abi 
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"returnValue",
"type":"bool"}],"name":"valueEvent","type":"event"},{"inputs":[{"internalType":"uint8",
"name":"x","type":"uint8"}],"name":"Matcher","outputs":[{"internalType":"bool",
"name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]

Tanto o bytecode, quanto a descrição da ABI serão utilizados na criação do script de deploy.

9.4.3 Web3 deployment: Criar um script de deployment

Iremos preparar o código do script JavaScript com as informações geradas pelo solc. Declarando um contrato utilizando a ABI e criando um novo contrato com o código binário do contrato compilado. Crie um arquivo deploy.js e salve o conteúdo do Código 9.VIII.

Listing 9.VIII: Script de deploy
var valuecheckerContract = web3.eth.contract([{ "anonymous": false, "inputs": [{ "indexed": false, "internalType": "bool", "name": "returnValue", "type": "bool" }], "name": "valueEvent", "type": "event" }, { "inputs": [{ "internalType": "uint8", "name": "x", "type": "uint8" }], "name": "Matcher", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" }]);
var valuechecker = valuecheckerContract.new({
    from: web3.eth.accounts[0],
    data: '0x6080604052600a60005534801561001557600080fd5b5061010d806100256000396000f3006
          08060405260043610603f576000357c01000000000000000000000000000000000000000000000
          00000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080f
          d5b50606f600480360381019080803560ff1690602001909291905050506089565b60405180821
          5151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a22
          9ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c600160405180821515151
          5815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a7230582
          09ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029', gas: '4700000'
},
    function(e, contract) {
        console.log(e, contract);
        if (!e) {
            if (typeof contract.address == 'undefined') {
                console.log("Contract transaction send: TransactionHash: " +
                contract.transactionHash + " waiting to be mined...");
            } else {
                console.log("Contract mined! Address: " + contract.address + ', transactionHash: ' + contract.transactionHash);
                console.log(contract);
            }
        }
    })

9.5 Web3 deployment: Fazendo o deploy pelo console do geth

Para fazer o deploy iremos copiar o código Javascript do arquivo criado e colar no console Javascript, conforme Código 9.IX.

Listing 9.IX: Deploy via console
> var valuecheckerContract = web3.eth.contract([{ "anonymous": false, "inputs": [{ "indexed": false, "internalType": "bool", "name": "returnValue", "type": "bool" }], "name": "valueEvent", "type": "event" }, { "inputs": [{ "internalType": "uint8", "name": "x", "type": "uint8" }], "name": "Matcher", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" }]);
undefined
var valuechecker = valuecheckerContract.new({
    from: web3.eth.accounts[0],
    data: '0x6080604052600a60005534801561001557600080fd5b5061010d806100256000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029', gas: '4700000'
},
    function(e, contract) {
        console.log(e, contract);
        if (!e) {
            if (typeof contract.address == 'undefined') {
                console.log("Contract transaction send: TransactionHash: " +
                contract.transactionHash + " waiting to be mined...");
            } else {
                console.log("Contract mined! Address: " + contract.address + ', transactionHash: ' + contract.transactionHash);
                console.log(contract);
            }
        }
    })

Atenção pois é necessário confirmar a transação no _console do clef.

Se na execução do deploy der uma mensagem de erro Error: insufficient funds for gas * price + value undefined, é porque a carteira da conta selecionada não tem saldo suficiente. É necessário minerar com miner.start() para gerar algum saldo e repetir o deploy.

  Error: insufficient funds for gas * price + value undefined
undefined
> 
> miner.start()
null
> miner.stop()
null
> eth.getAccounts()
undefined
> eth.accounts
["0x2db017e44b03b37755a4b15e14cd799f83de4c13", "0x7a7686ad451d2865a2246e239b674aefd4c6c27c", "0x1bba02873cc1c11f369a7b692f5f3de8ff7bbe80", "0x1170bbdc51d3791be6ba31dd6ed04383c146c2ed"]
> eth.getBalance("0x2db017e44b03b37755a4b15e14cd799f83de4c13")
166000000000000300000

Repetindo-se os comandos no console Javascript e após as confirmações no console do clef:

-------------------------------------------
Request context:
        NA -> ipc -> NA

Additional HTTP header data, provided by the external caller:
        User-Agent: ""
        Origin: ""
Approve? [y/N]:
> y
--------- Transaction request-------------
to:    <contact creation>
from:               0x2db017E44b03B37755A4b15e14Cd799f83DE4c13 [chksum ok]
value:              0 wei
gas:                0x47b760 (4700000)
gasprice: 1000000000 wei
nonce:    0x0 (0)
chainid:  0x312
data:     0x6080604052600a60005534801561001557600080fd5b5061010d806100256000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029

Request context:
        NA -> ipc -> NA

Additional HTTP header data, provided by the external caller:
        User-Agent: ""
        Origin: ""
-------------------------------------------
Approve? [y/N]:
> y
\#\# Account password

Please enter the password for account 0x2db017E44b03B37755A4b15e14Cd799f83DE4c13
> 
-----------------------
Transaction signed:
 {
    "type": "0x0",
    "nonce": "0x0",
    "gasPrice": "0x3b9aca00",
    "maxPriorityFeePerGas": null,
    "maxFeePerGas": null,
    "gas": "0x47b760",
    "value": "0x0",
    "input": "0x6080604052600a60005534801561001557600080fd5b5061010d806100256000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029",
    "v": "0x648",
    "r": "0x7085d0359b0cdef9c553d3e6f32a0a0a82dc0a376ed8fa18dae76dc93e274171",
    "s": "0x50f8eb26062a35825f0c960856cec0c3d20fab15ed9ab272f9ab85b59bdfedf8",
    "to": null,
    "hash": "0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081"
  }

No terminal de execução geth irá aparecer a mensagem de que o contrato foi submetido.

INFO [10-02|16:32:56.551] Submitted contract creation              hash=0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081 from=0x2db017E44b03B37755A4b15e14Cd799f83DE4c13 nonce=0 contract=0xe0203C7AEE6512789d63b54773dEDaCd84b1d06B value=0

E no terminal do console Javascript irá aparecer a mensagem que o contrato está aguardando ser minerado:

null [object Object]
Contract transaction send: TransactionHash: 0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081 waiting to be mined...
undefined
> 

Se iniciarmos a mineração novamente o contrato será minerado e o endereço atribuído a ele será apresentado 0xe0203c7aee6512789d63b54773dedacd84b1d06b:

> miner.start()
null
> null [object Object]
Contract mined! Address: 0xe0203c7aee6512789d63b54773dedacd84b1d06b, transactionHash: 0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081
[object Object]
> miner.stop()

9.6 Web3 deployment: Interagindo com o contrato

Após o deploy o contrato estará disponível no console. Podemos interagir com o contrato via console Javascript conforme o Código 9.X.

Listing 9.X: Interagindo com o Contrato
> valuechecker
valuechecker valuecheckerContract 
> valuechecker.
valuechecker.Matcher         valuechecker.allEvents       
valuechecker._eth            valuechecker.constructor     
valuechecker.abi             valuechecker.transactionHash 
valuechecker.address         valuechecker.valueEvent      
> valuechecker.address
"0xe0203c7aee6512789d63b54773dedacd84b1d06b"
> valuechecker.transactionHash
"0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081"

Percebam o mesmo address e transactionHash que foram devolvidos no processo de deploy.

A ABI do valuechecker está disponível conforme o Código 9.XI.

Listing 9.XI: Verificando a ABI do Contrato
> valuechecker.abi
[{
    anonymous: false,
    inputs: [{
        indexed: false,
        internalType: "bool",
        name: "returnValue",
        type: "bool"
    }],
    name: "valueEvent",
    type: "event"
}, {
    inputs: [{
        internalType: "uint8",
        name: "x",
        type: "uint8"
    }],
    name: "Matcher",
    outputs: [{
        internalType: "bool",
        name: "",
        type: "bool"
    }],
    stateMutability: "nonpayable",
    type: "function"
}]
> 

O código binário do contrato implantado pode ser recuperado no console javascript. O Código 9.XII apresenta o binário gerado para o Contrato.

Listing 9.XII: Código Binário do Contrato
> eth.getCode("0xe0203c7aee6512789d63b54773dedacd84b1d06b")
"0x608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029"
> 

A função Matcher pode ser invocada para a verificação de valores conforme mostrado no Código 9.XIII.

Listing 9.XIII: Chamada da função Matcher
> eth.getBalance(valuechecker.address)
0
> valuechecker.Matcher.call(12)
true
> valuechecker.Matcher.call(10)
true
> valuechecker.Matcher.call(5)
false
> 

Para chamar o contrato via eth.call(...) iremos precisar montar a chamada com os parâmetros. Supondo que queiramos chamar o método Matcher com o valor \(15\).

Serão passados \(68\) bytes no total subdivididos em:

  1. 0xf9d55e21: o ID do método que é derivado dos quatro primeiros bytes do Keccak hash da forma ASCII da assinatura do método Matcher(uint8). O keccak hash de Matcher(uint8) é igual a 0xf9d55e21d289cb5d19e2c60f6f1087d2d0cf1db6bb4037a139606a23599a61f3 que pode ser gerado no console JavaScript ou utilizando a string “Matcher(uint8)” no site https://emn178.github.io/online-tools/keccak_256.html.
> web3.sha3('Matcher(uint8)')
"0xf9d55e21d289cb5d19e2c60f6f1087d2d0cf1db6bb4037a139606a23599a61f3"
  1. 0x000000000000000000000000000000000000000000000000000000000000000f: o primeiro parâmetro, um valor uint32 do valor \(15\) padded para \(32\) _bytes$.

Então a chamada pode ser feita para o endereço do contrato implantado, o endereço apresentando no momento do deploy 0xe0203c7aee6512789d63b54773dedacd84b1d06b, pode ser recuperado pelo console Javascrip com o comando valuechecker.address. No campo data passamos a codificação da assinatura do método mais o valor a ser verificado como parâmetro.

No [Código Listing 9.XIV} é feita a chamada para a função Matcher(15) e o resultado 0x0000000000000000000000000000000000000000000000000000000000000001 representa o valor True.

Listing 9.XIV: Chamada para Matcher(15)
> web3.eth.call({
    to: "0xe0203c7aee6512789d63b54773dedacd84b1d06b",
    data: "0xf9d55e21000000000000000000000000000000000000000000000000000000000000000F"
});
"0x0000000000000000000000000000000000000000000000000000000000000001"
>

No Código 9.XV a mesma chamada é codificado e feita com o valor \(5\) e o resultado é do valor 0x0000000000000000000000000000000000000000000000000000000000000000 que representa \(0\), logo False.

Listing 9.XV: Chamada para Matcher(5)
> web3.eth.call({
    to: "0xe0203c7aee6512789d63b54773dedacd84b1d06b",
    data: "0xf9d55e210000000000000000000000000000000000000000000000000000000000000005"
});
"0x0000000000000000000000000000000000000000000000000000000000000000"

No console Javascript ou em um código de um script que possa ser executado em um Navegador:

var result = web3.eth.call({
    to: "0xe0203c7aee6512789d63b54773dedacd84b1d06b",
    data: "0xf9d55e21000000000000000000000000000000000000000000000000000000000000000F"
});
undefined
> console.log(result);
0x0000000000000000000000000000000000000000000000000000000000000001
null
> result
"0x0000000000000000000000000000000000000000000000000000000000000001"
> 

9.7 Deploy de Contrato com Transação de Contract Creation

O Código 9.XVI apresenta o deploy do código binário do contrato via transação.

Listing 9.XVI: Deploy do Código do Contrato via transação
> src = web3.eth.accounts[0]
"0x2db017e44b03b37755a4b15e14cd799f83de4c13"
> contract_code = "0x608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029"
"0x608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029"
> web3.eth.sendTransaction({from: src, data: contract_code, gas: 113558, gasPrice: 200000000000})
"0xf374e98fb632d5036f56ad5674e8f304fcb812fcd1be790f370e9e8a9adf806e"
> 
Approve? [y/N]:
> y
## Account password

Please enter the password for account 0x2db017E44b03B37755A4b15e14Cd799f83DE4c13
> 
-----------------------
Transaction signed:
 {
    "type": "0x0",
    "nonce": "0x11",
    "gasPrice": "0x2e90edd000",
    "maxPriorityFeePerGas": null,
    "maxFeePerGas": null,
    "gas": "0x1bb96",
    "value": "0x0",
    "input": "0x608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063f9d55e21146044575b600080fd5b348015604f57600080fd5b50606f600480360381019080803560ff1690602001909291905050506089565b604051808215151515815260200191505060405180910390f35b600080548260ff1610151560db577f3eb1a229ff7995457774a4bd31ef7b13b6f4491ad1ebb8961af120b8b4b6239c6001604051808215151515815260200191505060405180910390a16001905060dc565b5b9190505600a165627a7a723058209ff756514f1ef46f5650d800506c4eb6be2d8d71c0e2c8b0ca50660fde82c7680029",
    "v": "0x647",
    "r": "0xf323a86945330628a8746a8a079639de0d6d6f5ad65c006c16c09b3c50f3ff5e",
    "s": "0x3b0d13eaac7523fea6e22b0df01724232999d7ac22b3993464348bebc8059874",
    "to": null,
    "hash": "0xf374e98fb632d5036f56ad5674e8f304fcb812fcd1be790f370e9e8a9adf806e"
  }
INFO [10-23|15:40:23.825] Submitted contract creation              hash=0xf374e98fb632d5036f56ad5674e8f304fcb812fcd1be790f370e9e8a9adf806e from=0x2db017E44b03B37755A4b15e14Cd799f83DE4c13 nonce=17 contract=0x2Ec04170e7b52d40D494F149Bd5E29e0Bf0c8Dd8 value=0

9.8 Interagir com o contrato via um frontend web.

Podemos utilizar qualquer uma das redes locais ou simuladas para testar os comandos RPC. Primeiro utilizaremos a rede simulada do ganache-ui da Aula 07: Simulando uma Rede Ethereum Local com Ganache e Metamask. Executando a versão appmimage, versão atual ganache-2.7.1-linux-x86_64.AppImage, que pode ser executada sem a necessidade de instalação.

Executando o ganache-ui e indo na aba da lista de contas, conforme a Figura 9.II.

Figure 9.II: Lista de Contas

O comando RPC para a recuperação de contas via terminal utilizando o curl pode ser visto no Código 9.XVII.

Listing 9.XVII: Comando RPC para recuperação das contas
$ curl -X POST --insecure --header "Content-Type: application/json" --location http://localhost:7545  --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":83}'
{"id":83,"jsonrpc":"2.0","result":["0x94c5ce22ba980e37c0b3a5105fec398ddcba711d",
"0x7fd63437b01daf6674c11f77a0a3e86dd2291221","0x8550abcf33308c521d8cd660a57ea2c9184dd33e",
"0xbb940857f76e328cfb9c36ba4c3a7b301d432f40","0x335c3af881896f490bfabf819c2200a9289eaace",
"0x91c2283b5053111baddc60cdd5f7db44b52614d7","0x3d806bdec35c37e0771bd6d67363c7b2d36eb631",
"0x91b318a4e2ec781404f1334698a897f2aceb3284","0xa7b91fe78a00c2003b49b92a39fb90e2fb67463c",
"0x05660e2dde69604288c632cc7b98e3f29d054316"]}

A lista de blocos pode ser vista na Figura 9.III. O número do bloco mais recente presente na lista no meu caso é o \(2\) (hexadecimal \(0x2\)).

Figure 9.III: Lista de Blocos

O número do último bloco pode ser recuperado utilizando o eth.blockNumber via RPC com o comando:

$ curl -X POST --insecure --header "Content-Type: application/json" --location http://localhost:7545  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}'
{"id":83,"jsonrpc":"2.0","result":"0x2"}

Recuperar o recibo de uma transação: link

$ curl -X POST --insecure --header "Content-Type: application/json" --location http://localhost:7545 --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":
["0xc22b72d8469b0dc3676e61dfd09ead8faa3ff08a0f67e622232070587158cc9e"],"id":1}'
{"id":1,"jsonrpc":"2.0","result":{"transactionHash":
"0xc22b72d8469b0dc3676e61dfd09ead8faa3ff08a0f67e622232070587158cc9e",
"transactionIndex":"0x0","blockNumber":"0x2",
"blockHash":"0xf00b1600c68890bbf9a953bdaa87551072ccf1081febb99e2d5f03bf49f11cff",
"from":"0x94c5ce22ba980e37c0b3a5105fec398ddcba711d",
"to":"0x7fd63437b01daf6674c11f77a0a3e86dd2291221",
"cumulativeGasUsed":"0x5208","gasUsed":"0x5208","contractAddress":null,"logs":[],
"logsBloom":"0x0000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"status":"0x1","effectiveGasPrice":"0x4a817c800","type":"0x2"}}

Mais comandos da API RPC podem ser consultados na documentação JSON-RPC API.

Todos os comandos que vimos até o momento para a execução continuam válidos. Iremos testar o deploy na Rede Privada Local que criamos e com a Rede Simulada do Ganache.

Vimos que é possível interagir com o cliente de execução geth via POST requests utilizando a API JSON RPC sobre o HTTP. Para esse teste utilizaremos o curl. Lembrando que a porta utilizando foi a \(8559\).

Vimos que a lista de contas podem ser recuperadas com o comando:

[.etherprivate]$ curl -X POST --insecure --header "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_accounts","params":[], "id":64}' --location http://localhost:8559
{"jsonrpc":"2.0","id":64,"result":["0x2db017e44b03b37755a4b15e14cd799f83de4c13","0x7a7686ad451d2865a2246e239b674aefd4c6c27c","0x1bba02873cc1c11f369a7b692f5f3de8ff7bbe80"]}
[.etherprivate]$ 

Um objeto JSON é retornado com a lista de contas.

No comando curl, o parâmetro --request é usado para especificar que o comando é uma requisição do tipo POST e --data é usado para especificar os parâmetros e valores. Finalmente, o localhost:8559 é usando para indicar o endereço que o HTTP endpoint do geth está respondendo.

Uma estimativa de custo para a o deploy do contrato poderia ser dada pela função eth_estimateGas via RPC:

$ curl -X POST --insecure --header "Content-Type: application/json" --location http://localhost:8559 --data '{"jsonrpc":"2.0","method": "eth_estimateGas", "params": [{"from": "0x94c5Ce22ba980e37C0B3a5105FEc398dDCbA711d", "data": "0x6060604052341561000f57600080fd5b60eb8061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa1146044575b600080fd5b3415604e57600080fd5b606260048080359060200190919050506078565b6040518082815260200191505060405180910390f35b60007f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da600783026040518082815260200191505060405180910390a16007820290509190505600a165627a7a7230582040383f19d9f65246752244189b02f56e8d0980ed44e7a56c0b200458caad20bb0029"}], "id": 5}'
{"jsonrpc":"2.0","id":5,"result":"0x1959e"}
$ echo $((0x1959e))
103838

E o deploy do contrato:

$ curl -X POST --insecure --header "Content-Type: application/json" --location http://localhost:7545 --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0x94c5Ce22ba980e37C0B3a5105FEc398dDCbA711d", "gas": "0x1c31e", "data": "0x6060604052341561000f57600080fd5b60eb8061001d6000396000f300606060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063c6888fa1146044575b600080fd5b3415604e57600080fd5b606260048080359060200190919050506078565b6040518082815260200191505060405180910390f35b60007f24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da600783026040518082815260200191505060405180910390a16007820290509190505600a165627a7a7230582040383f19d9f65246752244189b02f56e8d0980ed44e7a56c0b200458caad20bb0029"}], "id": 6}'
{"id":6,"jsonrpc":"2.0","result":"0x15d7ed0c9f19e98f6d8d82fd8a658d10fb3c4cdd7b510fbfb577b062f81bf9a7"}$ 

A transação 0x15d7ed0c9f19e98f6d8d82fd8a658d10fb3c4cdd7b510fbfb577b062f81bf9a7 de Contract Creation foi inserida na lista de transações, conforme Figura 9.IV.

Figure 9.IV: Transação de Criação de Contrato

Para chamar o contrato via RPC iremos precisar montar a chamada com os parâmetros. Supondo que queiramos chamar o método Matcher com o valor \(15\).

Serão passados \(68\) bytes no total subdivididos em:

  1. 0xf9d55e21: o ID do método que é derivado dos quatro primeiros bytes do Keccak hash da forma ASCII da assinatura do método Matcher(uint8). keccak: 0xf9d55e21d289cb5d19e2c60f6f1087d2d0cf1db6bb4037a139606a23599a61f3 que pode ser gerado no console JavaScript ou utilizando a string “Matcher(uint8)” no site https://emn178.github.io/online-tools/keccak_256.html.
> web3.sha3('Matcher(uint8)')
"0xf9d55e21d289cb5d19e2c60f6f1087d2d0cf1db6bb4037a139606a23599a61f3"
  1. 0x000000000000000000000000000000000000000000000000000000000000000f: o primeiro parâmetro, um valor uint32 do valor \(15\) padded para \(32\) _bytes$.
$ curl --location http://localhost:8559 -X POST --insecure --header "Content-Type: application/json" --data '{"jsonrpc":"2.0", "method":"eth_call", "params":[{"from": "0x2db017e44b03b37755a4b15e14cd799f83de4c13", "to": "0xe0203c7aee6512789d63b54773dedacd84b1d06b", "data": "0xf9d55e21000000000000000000000000000000000000000000000000000000000000000f"}, "latest"], "id":1}'
{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000001"}
$ curl --location http://localhost:8559 -X POST --insecure --header "Content-Type: application/json" --data '{"jsonrpc":"2.0", "method":"eth_call", "params":[{"from": "0x2db017e44b03b37755a4b15e14cd799f83de4c13", "to": "0xe0203c7aee6512789d63b54773dedacd84b1d06b", "data": "0xf9d55e210000000000000000000000000000000000000000000000000000000000000005"}, "latest"], "id":1}'
{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000000"}

9.9 Utilizando o REMIX IDE para os deploys

A REMIX é uma IDE que funciona online, sem a necessidade de instalação. A Figura 9.V apresenta a tela inicial da ferramenta que pode ser acessada em https://remix.ethereum.org/.

Figure 9.V: Interface do remix.ethereum.org

Na interface do REMIX podemos configurar a versão do compilador solc conforme apresentado na Figura 9.VI, a versão atual apresentada é a 0.8.27+commit.40a35a09, porém por estarmos executando a versão 1.11.6 do geth, a EVM é a Paris, logo para o conjunto de instruções ser compatível é necessário que compilemos com a versão 0.8.19 do solc.

Figure 9.VI: Configurando Versão do solc

O tipo de ambiente de deploy e a conta principal podem ser escolhidos na tela apresentada na Figura 9.VII.

Figure 9.VII: Configurando Ambiente de Deploy}

O ambiente para deploy e execução de transações pode ser escolhido conforme Figura 9.VIII.

Figure 9.VIII: Configurando Ambiente de Deploy

Dentre os possíveis temos: Injected Provider - Metamask, Remix VM (Cancun), Remix VM (Mainnet fork), Remix VM (Sepolia fork), Remix VM (Shangai), Dev - Hardhat Provider e Dev - Ganache Provider, entre outros que podem ser inseridos na lista na opção Customize this list….

9.9.1 Executando o geth para aceitar conexão com o REMIX

É necessário que executemos o geth para aceitar conexão com REMIX e nas configurações da IDE indicar a rede que será utilizada.

Para aceitar conexões da IDE REMIX inicie o cliente de execução geth com alguns parâmetros extras -http.corsdomain="https://remix.ethereum.org" --vmdebug, o Código 9.XVIII.

Listing 9.XVIII: Comando geth para conexão com REMIX
$ $HOME/go-ethereum-1.11.6/build/bin/geth --networkid 786 --datadir ~/.etherprivate-ethash/ --syncmode full --allow-insecure-unlock  --identity "RAGEtherPrivate"  --http --http.addr 127.0.0.1 --http.port 8559 --http.api "eth,net,web3,personal,engine,admin,debug,miner,txpool" --http.corsdomain="https://remix.ethereum.org" --vmdebug --keystore ~/.etherprivate-ethash/keystore --authrpc.addr localhost --authrpc.port 8551 --authrpc.vhosts localhost --authrpc.jwtsecret ~/.etherprivate-ethash/geth/jwtsecret --nodiscover --maxpeers 15 --miner.etherbase=0x2db017e44b03b37755a4b15e14cd799f83de4c13 --signer=$HOME/.etherprivate-ethash/clef/clef.ipc --dev console

9.9.2 Conectando o REMIX ao Metamask

Caso não tenha uma rede conectada ao Metamask acesse a Aula 07: Simulando uma Rede Ethereum Local com Ganache e Metamask.

Na interface do Remix, a conexão com o Metamask pode ser feita utilizando a opção Injected Provider - Metamask, que por sua vez está conectado à Rede Privada Local. Escolha uma conta entre as listadas, a conta \(0\) que é retornada no eth.accounts.

Conexão com o Metamask

A rede será detectada e podemos escolher uma conta entras as conectadas no Metamask para ser a conta que receberá cobrança pelos deploys, conforme Figura 9.IX.

Figure 9.IX: Remix conectado ao Metamask}

Agora temos o Remix configurado e conectado à Rede Local via Metamask.

9.10 Fazendo o deploy pelo REMIX

Uma vez conectado à Rede Privada Local via Metamask é possível fazer deploy do contrato. Clicando em Deploy e fazendo as confirmações no Metamask.

Deploy do Contrato

A implantação aparecerá como pendente.

Implantação (Pendente)

O processo de mineração pode ser acelerado, clicando na opção eu salvando.

Acelerar o processamento
INFO [10-03|23:17:32.431] Submitted contract creation              hash=0xca3fede5fa596e4b93f50002d81fc1d025d6220fc984400d3be94508adbff802 from=0x2db017E44b03B37755A4b15e14Cd799f83DE4c13 nonce=6 contract=0x0c792147d011841783Da2C53815A7Dc9a19Cc9FC value=0

Um erro apareceu no console da REMIX.

creation of valueChecker pending...
creation of valueChecker errored: Error occurred: Transaction started at 440 but was not mined within 122 blocks. Please make sure your transaction was properly sent and there no pervious pending transaction for the same account. However, be aware that it might still be mined!
    Transaction Hash: 0xc2efdbae4744516ce7c9c17dbe9bf766f21604c4d1b9f39550a487028cf5a100.

Transaction started at 440 but was not mined within 122 blocks. Please make sure your transaction was properly sent and there no pervious pending transaction for the same account. However, be aware that it might still be mined!
    Transaction Hash: 0xc2efdbae4744516ce7c9c17dbe9bf766f21604c4d1b9f39550a487028cf5a100

You may want to cautiously increase the gas limit if the transaction went out of gas.

Erro por gas limite

Inicie a mineração e deixe ser acumulado algum saldo na carteira, se o saldo for suficiente o contrato será minerado:

> miner.start()
null
> null [object Object]
Contract mined! address: 0xe0203c7aee6512789d63b54773dedacd84b1d06btransactionHash: 0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081

> miner.stop()

Tentando o deploy novamente, o erro ainda ocorre:

creation of valueChecker pending...
creation of valueChecker errored: Error occurred: Transaction started at 1825 but was not mined within 92 blocks. Please make sure your transaction was properly sent and there no pervious pending transaction for the same account. However, be aware that it might still be mined!
    Transaction Hash: 0x5cd14a7785878c22c8eaec8da1124ca574babcdaec44c4b66ecd1a6b33838899.

Transaction started at 1825 but was not mined within 92 blocks. Please make sure your transaction was properly sent and there no pervious pending transaction for the same account. However, be aware that it might still be mined!
    Transaction Hash: 0x5cd14a7785878c22c8eaec8da1124ca574babcdaec44c4b66ecd1a6b33838899

You may want to cautiously increase the gas limit if the transaction went out of gas.

https://support.metamask.io/pt-br/transactions-and-gas/gas-fees/why-did-my-transaction-fail-with-an-out-of-gas-error-how-can-i-fix-it/

Enquanto isso você pode acompanhar o status da transação pelo hash.

> eth.getTransaction("0x56482105054dd8d57f0cea35a58544d8327891870347c62d03853456bedb724c")
{
  blockHash: null,
  blockNumber: null,
  chainId: "0x312",
  from: "0x2db017e44b03b37755a4b15e14cd799f83de4c13",
  gas: 183137,
  gasPrice: 1331000000,
  hash: "0x56482105054dd8d57f0cea35a58544d8327891870347c62d03853456bedb724c",
  input: "0x",
  nonce: 4,
  r: "0xbd884444908c5f0b97631bbf93e2c0fa451522a9a17d1cced4b8019201168161",
  s: "0x5137f387d7c3159b20533fc99c2b3dfad7c231b3e46e0e2bc78c058ebc0145d2",
  to: "0x2db017e44b03b37755a4b15e14cd799f83de4c13",
  transactionIndex: null,
  type: "0x0",
  v: "0x647",
  value: 0
}
> 

Quando o contrato for minerado um retorno no console será apresentando:

> miner.start()
null
> null [object Object]
Contract mined! address: 0xe0203c7aee6512789d63b54773dedacd84b1d06btransactionHash: 0xd3422f91fc4063682fcaed37cc7eb8b7b438cae9f0c9b56596bc49ededaf2081

> miner.stop()

9.11 Conectando o REMIX ao Ganache

O Remix pode se conectar diretamente ao Ganache através da opção Dev - Ganache Provider, precisa estar executando com o mesmo endereço e porta que o Ganache está respondendo.

Configurando Remix para acessar o Ganache

Ao fazermos o deploy do contrato irá aparecer na lista de transações como CONTRACT CREATION.

Deploy de Contrato

Quando o contrato for implantado vai aparecer no bloco que foi minerado na lista de blocos do Ganache.

Bloco Minerado

Se forem feitas chamadas à função do contrato implantado, essas transações irão aparecer como CONTRACT CALL na lista de transações.

Contract Call

9.12 Interagindo com contratos via frontends web 

A interação com smart contracts como parte de uma DApps é normalmente feito usando uma interface web desenvolvida utilizando HTML/JS/CSS. Algumas bibliotecas e frameworks como React, Redux, e Drizzle, podem também ser usadas. A Figura 9.X apresenta uma arquitetura básica de funcionamento de uma aplicação web que se comunica com a blockchain do Ethereum via comandos de cobertura da biblioteca web3.

Figure 9.X: Interação com Aplicações Web

9.13 Considerações Finais

Nesta prática fizemos o deploy ou implantação de um exemplo de contrato, via console Javascript, via chamada RPC e utilizando a REMIX conectada à Rede Privada Local via Metamask. Todas as redes privadas locais ou simuladas que testamos podem ser utilizadas para a execução de comandos de implantação de contratos.

9.14 Leitura Recomendada

10 Word Cloud

10.1 Referências