WEB3DEV

Cover image for Contrato inteligente. Solidity + Ganache
Panegali
Panegali

Posted on

Contrato inteligente. Solidity + Ganache

Transação de criação de contrato

Neste artigo, aprenderemos como implantar um contrato inteligente muito simples na blockchain Ganache local. Depois de implantar um contrato inteligente, aprenderemos como interagir com ele enviando transações para seu endereço. Para simplificar, usarei o framework Truffle, pois simplifica a implantação e a interação com o contrato inteligente.

Como um contrato, criaremos a torneira (faucet) mais simples. Em condições reais, a torneira é usada como um armazenamento de criptomoedas, a partir do qual qualquer pessoa pode transferir uma certa quantia de fundos para seu saldo. Torneiras são usadas em redes de teste. Em um dos meus artigos anteriores, mostrei como usar uma torneira real.

Lógica do contrato inteligente

Nosso contrato terá apenas duas funções:

  • Uma para adicionar Ether ao saldo do contrato inteligente.
  • A outra, para transferir Ether do saldo do contrato inteligente para a conta que chamou essa função. A quantidade necessária de Ether será passada no argumento da função.

A segunda função terá um limite - não mais que 0,01 ETH por transação. Se for solicitado mais, a transação falhará.

Nota: mesmo que a transação termine com um erro, ainda será cobrado do remetente algum Ether pelo envio da transação. Esta é uma taxa para o gás necessário para processar a transação nos nós. Assim, uma carga injusta na rede é excluída, quando um invasor carrega a rede com transações obviamente errôneas e desperdiça seu poder de computação. Ele ainda pagará por tais transações e, mais cedo ou mais tarde, usará todo o seu Ether. Portanto, por exemplo, um contrato com loop infinito não causará muitos danos aos recursos da blockchain.

Plano

  1. Criar um contrato.
  2. Completar o saldo do contrato da conta A.
  3. Chamar o contrato da conta B e retirar alguns Ether do saldo do contrato.

Isso pressupõe que você já tenha o Ganache e o Truffle instalados. Caso contrário, você pode verificar a instalação em meu artigo anterior. Se conceitos como EOA (Conta de Propriedade Externa), conta de contrato, transação, gás não são familiares para você, consulte meus artigos anteriores, cujo link deixarei no final do guia.

Então, vamos começar e criar um contrato.

Etapa 1. Criando um contrato inteligente

Crie um diretório de trabalho e inicialize o projeto no Truffle:

$ mkdir simple-faucet
$ cd simple-faucet
$ truffle init
Enter fullscreen mode Exit fullscreen mode

Após inicializar o projeto, nosso diretório de trabalho ficará assim:

.
├── contracts
├── migrations
├── test
└── truffle-config.js

4 directories, 1 file
Enter fullscreen mode Exit fullscreen mode

Trataremos da finalidade desses diretórios ao longo do caminho, mas por enquanto, na pasta de contratos, criaremos nosso contrato e o chamaremos de Faucet.sol:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

contract Faucet {
    // Aceita qualquer valor de entrada
    receive() external payable {}

    // Distribui ether para qualquer pessoa que pedir
    function withdraw(uint withdraw_amount) public {
        // Limita o valor do saque
        require(withdraw_amount <= 0.01 ether);

        // Envia o valor para o endereço que o solicitou
        payable(msg.sender).transfer(withdraw_amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

A segunda linha, pragma solidity define a compatibilidade do contrato com diferentes versões do compilador. No meu caso, especifiquei todas as versões 0.8.X. O Solidity é uma linguagem em rápido desenvolvimento e, via de regra, as versões 0.X e 0.Y são incompatíveis entre si. Se você tentar compilar nosso contrato com, digamos, versão do compilador 0.7.6, o compilador emitirá imediatamente um erro de compatibilidade após ler a linha pragma solidity e não prosseguirá com a compilação.

Eu compilei o contrato no Solidity 0.8.20, que era a versão mais recente no momento da redação. Você pode especificar a versão do compilador no arquivo truffle-config.js e, ao compilar, o framework Truffle puxará automaticamente a versão necessária.

Fragmento do arquivo de configuração truffle-config.js com a versão do compilador 0.8.20:

// Configure seus compiladores
  compilers: {
    solc: {
      version: "0.8.20",
      // ...
Enter fullscreen mode Exit fullscreen mode

Vamos compilar nosso contrato:

$ truffle compile
Enter fullscreen mode Exit fullscreen mode

Após a compilação bem-sucedida, o arquivo Faucet.json deve aparecer na pasta build/contracts - este é um arquivo para as necessidades internas da estrutura, necessário para implantar um contrato inteligente na blockchain, bem como para simplificar a interação com o contrato do código do aplicativo.

Em seguida, precisamos informar ao framework Truffle qual contrato implantar. Para fazer isso, na pasta migrations, crie o arquivo 1_faucet_migration.js e adicione o seguinte código a ele:

var Faucet = artifacts.require("Faucet");

module.exports = function(deployer) {
  deployer.deploy(Faucet);
};
Enter fullscreen mode Exit fullscreen mode

Após essas etapas, seu ambiente deve ficar assim:

Contrato e estrutura do projeto Truffle

Estamos quase prontos para implantar o contrato. Resta executar o Ganache e adicionar uma configuração.

Vamos começar o Ganache:

O estado inicial da blockchain

Para que o Ganache consiga visualizar o contrato inteligente, precisamos apontá-lo para o arquivo de configuração do nosso projeto. Nome do arquivo de configuração: truffle-config.js. Para fazer isso, vá para a aba 'Contracts' (Contratos), e clique em LINK TRUFFLE PROJECTS (Vincular os Projetos do Truffle):

Aba Contracts. Sem contratos ainda

Na janela que aparece, clique em ADD PROJECT (Adicionar Projeto), localize a pasta com nosso projeto e aponte para o arquivo de configuração truffle-config.js:

Janela para adicionar um projeto com um contrato

Em seguida, clique em SAVE AND RESTART (Salvar e Reiniciar).

Após o reinício, as informações sobre nosso contrato devem aparecer na guia 'Contracts'. Como você pode ver, ele ainda não foi implantado:

O contrato aparecerá, mas ainda não foi implantado

Ótimo. Agora estamos todos prontos para implantar o contrato. Para fazer isso, digite no console:

$ truffle migrate
Enter fullscreen mode Exit fullscreen mode

Saída:

Starting migrations...
======================
> Network name:    'ganache'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_faucet_migration.js
=====================
⠇ Fetching solc version list from solc-bin. Attempt #1
   Deploying 'Faucet'r. Attempt #1.
   ------------------
   > transaction hash:    0x326495056d8c8ae784b1bb35c448f65817bda7a6d0f7d4eb6db2b06b7dd02fbdg compiler. Attempt #1.
   > Blocks: 0            Seconds: 0
   > contract address:    0x38f409e4A974A7A0B19F707BDCFF56dC3F6Eb0a4
   > block number:        1
   > block timestamp:     1686649344
   > account:             0x5591B981a1133b044B36d82502e838f597b0af6D
   > balance:             99.999573572125
   > gas used:            126349 (0x1ed8d)
   > gas price:           3.375 gwei
   > value sent:          0 ETH
   > total cost:          0.000426427875 ETH

   > Saving artifacts
   -------------------------------------
   > Total cost:      0.000426427875 ETH

Summary
=======
> Total deployments:   1
> Final cost:          0.000426427875 ETH
Enter fullscreen mode Exit fullscreen mode

Excelente. O contrato está implantado. Bem abaixo, na seção 'Summary' (Resumo), vemos o campo 'Final Cost' (Custo final) e, ao lado dele, o valor em Ether, igual a 0,000426427875 ETH. Esta é uma taxa na forma de gás para a transação de criação do contrato. E agora a pergunta. Quem pagou essa taxa?

Se olharmos para cima, veremos o campo account: 0x5591B981a1133b044B36d82502e838f597b0af6D, e abaixo dele há um campo balance: 99.999573572125. Vamos até o Ganache encontrar esta conta. Esta será a primeira conta. Por que exatamente na primeira conta, não especificamos nenhum dado adicional no Truffle ao implantar o contrato? A resposta é que o Ganache tem uma conta padrão e é a primeira conta padrão. Abaixo, mostrarei como fazer transações em nome de outras contas.

Como o contrato foi implantado e descobrimos de quem foi cobrada a taxa pela criação do contrato, vamos até o Ganache verificar se o saldo da primeira conta diminuiu:

A GUI (Interface Gráfica do Usuário) do Ganache não reflete as alterações de saldo em pequenas quantidades

Parece que algo deu errado. Como você pode ver, o saldo não mudou. O fato é que o Ganache, ao mostrar o saldo, arredonda para cima e, portanto, não vemos a subtração de pequenas quantias. Para saber o saldo exato, podemos visualizá-lo no console simplesmente chamando a biblioteca web3.js, que faz parte do framework Truffle. Para fazer isso, entraremos no console Truffle:

$ truffle console
Enter fullscreen mode Exit fullscreen mode

Esta linha deve aparecer:

truffle(ganache)>
Enter fullscreen mode Exit fullscreen mode

Nos exemplos de código abaixo, se estivermos falando sobre chamar comandos no console Truffle, escreverei o símbolo > antes de chamar o comando.

Verificando o saldo:

> web3.eth.getBalance('0x5591B981a1133b044B36d82502e838f597b0af6D');

// Out: '99999573572125000000'
Enter fullscreen mode Exit fullscreen mode

Ótimo, vemos que o saldo mudou exatamente pela quantidade de Ether que estava no campo 'Final Cost' (Custo final). Para sair do console Truffle, você pode usar Ctrl + D. Mas ainda vamos precisar dele nas próximas etapas, então você não pode fechá-lo por enquanto.

Voltemos ao Ganache e vejamos o status do nosso contrato:

Estado do contrato: DEPLOYED (Implantado)

Estado alterado para DEPLOYED. Vamos cair no contrato em si. Vemos o endereço do contrato e seu saldo:

Saldo do contrato criado: 0 ETH

Podemos ir até a aba 'Transactions' (Transações) e olhar a própria transação que criou o contrato:

Transação de criação do contrato

O contrato foi implantado, o que significa que é hora de adicionar um pouco de Ether ao seu saldo.

Etapa 2. Completar o saldo do contrato

Vamos aumentar o saldo do nosso contrato com, digamos, 70 Ether. Vamos transferir fundos da primeira conta. O endereço do remetente e o endereço do contrato podem ser obtidos na GUI do Ganache.

Transação que aumentou o saldo do contrato:

> web3.eth.sendTransaction({from: "0x5591B981a1133b044B36d82502e838f597b0af6D", to: "0x38f409e4A974A7A0B19F707BDCFF56dC3F6Eb0a4", value: web3.utils.toWei("70", "ether")});
Enter fullscreen mode Exit fullscreen mode

Recibo da transação:

{
  transactionHash: '0xf07947fc8346f297cde01e75ce241c090cc7beeee285b2df13fce213f4c416e5',
  transactionIndex: 0,
  blockNumber: 2,
  blockHash: '0x7d3aae3081f531a87026ab3d7b52c87fe33ded0e4ab65e1e56a0fbb997265cff',
  from: '0x5591b981a1133b044b36d82502e838f597b0af6d',
  to: '0x38f409e4a974a7a0b19f707bdcff56dc3f6eb0a4',
  cumulativeGasUsed: 21055,
  gasUsed: 21055,
  contractAddress: null,
  logs: [],
  logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  status: true,
  effectiveGasPrice: 3269736716,
  type: '0x2'
}
Enter fullscreen mode Exit fullscreen mode

A transação foi bem-sucedida. Vamos verificar os saldos da EOA e do contrato.

Saldo da primeira conta:

> web3.eth.getBalance("0x5591B981a1133b044B36d82502e838f597b0af6D");

// Saída: '29999504727818444620'
Enter fullscreen mode Exit fullscreen mode

Saldo do contrato:

> web3.eth.getBalance("0x38f409e4A974A7A0B19F707BDCFF56dC3F6Eb0a4");

// Saída: '70000000000000000000'
Enter fullscreen mode Exit fullscreen mode

No Ganache, também vemos que 70 Ether foram cobrados do remetente:

Primeiro saldo da conta após o envio de 70 ETH para o saldo do contrato

E o saldo do contrato aumentou em 70 Ether:

Saldo do contrato aumentado em 70 ETH

Uma observação importante. Enviamos Ether para o contrato como uma transação normal, o que não é diferente de transferir fundos de uma conta EOA para outra conta EOA, e não chamamos nenhuma função de contrato. Isso não significa que o contrato, como o EOA, sempre pode aceitar Ether em sua conta. Aqui, uma espécie de função de fallback entrou em jogo - a função receive(), que foi especialmente adicionada ao Solidity precisamente com o propósito de transferir explicitamente o Ether para o contrato.

Existe outro tipo de função de fallback, e ela existia antes da introdução da função receive(). Com ela, você pode transferir fundos para um contrato da mesma forma:

fallback() external payable {}
Enter fullscreen mode Exit fullscreen mode

A função fallback() funciona quando chamamos um método específico, mas ela não está presente no contrato chamado, e se houver Ether no campo value da transação, então eles serão adicionados ao saldo do contrato. Os fundos serão adicionados ao contrato somente se a palavra-chave payable estiver presente.

Se decidirmos enviar Ether para um contrato que não tenha nenhuma função de fallback, esse contrato não poderá aceitar Ether por meio de transações normais e a transação terminará com um erro.

Um contrato pode conter duas funções de fallback ao mesmo tempo. A lógica dessas funções pode ser resumida como um diagrama:

        Ether enviado para o contrato
                  |
            msg.data vazio ?
                 / \
               sim  não
              /      \
  receive() existe?   fallback()
            / \
          sim  não
          /     \
    receive()  fallback()
Enter fullscreen mode Exit fullscreen mode

msg.data é o campo de dados no corpo da transação, que geralmente contém informações sobre o método que está sendo chamado e seus argumentos e, no caso de criação de um contrato, o código do próprio contrato.

Assim, a função receive() foi criada especificamente para casos de envio de Ether para o saldo do contrato, para que a função fallback() mais antiga não execute várias tarefas ao mesmo tempo (princípio da responsabilidade única).

Nota: Ao aprender, você pode usar um IDE da Web, como o Remix IDE, para aprender rapidamente o comportamento de um contrato. Ferramentas como o Truffle e o Ganache são necessárias para o processo de desenvolvimento e testes automatizados.

Ótimo, nossa torneira agora tem fundos em seu saldo e pode servir ao seu propósito, que é permitir que outros usem esses fundos.

Etapa 3. Retirada de Ether do saldo do contrato

Nesta etapa, acessaremos o contrato da segunda conta e chamaremos seu método withdraw() para retirar 0,01 Ether do contrato. Para interagir com o contrato, usarei a abstração sobre o contrato, que obtenho usando o framework Truffle, pois isso facilitará o envio de uma transação.

Para obter a abstração do nosso contrato, execute o seguinte código no console do Truffle:

> const instance = await Faucet.deployed();
Enter fullscreen mode Exit fullscreen mode

Agora podemos acessar o método withdraw() do contrato com uma simples chamada:

> instance.withdraw(web3.utils.toWei("0.01", "ether"), {from: '0xc2AdBa94A888cB8c25f48b4b3dAd91F751617157'});
Enter fullscreen mode Exit fullscreen mode

Vamos prestar atenção ao objeto adicional com o campo from, que passamos como segundo argumento para a chamada do método. Não está na assinatura do método original no contrato. Com a ajuda deste objeto, indicamos ao framework que queremos executar uma transação de uma conta específica, no nosso caso da segunda. Se não tivéssemos passado esse objeto, a transação teria sido enviada da conta padrão, ou seja, da primeira.

Recibo da transação:

{
  tx: '0x0dda5e8f6c1ec125629a4c295765ce143d1be6d9ce91914cfe014b117ba00ef8',
  receipt: {
    transactionHash: '0x0dda5e8f6c1ec125629a4c295765ce143d1be6d9ce91914cfe014b117ba00ef8',
    transactionIndex: 0,
    blockNumber: 3,
    blockHash: '0xf65ea4b0e29960bd175eb61e7d32766bac7aee1ad33404d17b69070b131e4179',
    from: '0xc2adba94a888cb8c25f48b4b3dad91f751617157',
    to: '0x38f409e4a974a7a0b19f707bdcff56dc3f6eb0a4',
    cumulativeGasUsed: 28565,
    gasUsed: 28565,
    contractAddress: null,
    logs: [],
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    status: true,
    effectiveGasPrice: 3174122382,
    type: '0x2',
    rawLogs: []
  },
  logs: []
}
Enter fullscreen mode Exit fullscreen mode

Verificação de saldos.

Conta EOA após creditar 0,01 Ether do contrato:

> web3.eth.getBalance("0xc2AdBa94A888cB8c25f48b4b3dAd91F751617157");

// '100009909331194158170'
Enter fullscreen mode Exit fullscreen mode

Vemos que foi adicionado um pouco menos de 0,01 Ether, pois ao chamar o método do contrato, você precisa pagar pelo Gás.

Contrato após retirar 0,01 Ether dele:

> web3.eth.getBalance("0x38f409e4A974A7A0B19F707BDCFF56dC3F6Eb0a4");

// '69990000000000000000'
Enter fullscreen mode Exit fullscreen mode

Uma transação com uma chamada para o método withdraw():

A transação de chamar o método withdraw() em um contrato

Saldo do contrato após retirar 0,01 Ether:

0,01 ETH passou do saldo do contrato para a conta EOA

O saldo da segunda conta após adicionar 0,01 Ether do contrato:

O saldo da segunda conta após a transferência de 0,01 ETH do contrato para ela

Todas as nossas transações são de baixo para cima. Crie um contrato, transfira fundos para o contrato e chame o método de contrato para sacar fundos:

Todas as transações

Se implantarmos nosso contrato inteligente na rede de teste, qualquer pessoa poderá retirar o Ether dela ou adicionar Ether a ela. Como se conectar à rede de teste, eucontei aqui.

Isso é tudo. Nossa introdução às interações contratuais chegou ao fim. Aprendemos como criar um contrato na blockchain Ganache local, como interagir com um contrato e nos familiarizar com as funções de fallback.

Meus artigos anteriores:

  1. Local environment for learning Web3.js and Ethereum. 2023.
  2. Create, sign and send an Ethereum transaction manually using only Web3.js and Ganache. 2023 Denis.
  3. Connecting to the Ethereum Testnet using only web3.js and the console.
  4. Addresses in Ethereum.

Artigo escrito por Denis Avtsin. Traduzido por Marcelo Panegali.

Oldest comments (0)