WEB3DEV

Cover image for Como usar o Docker no seu Contrato Solidity do Hardhat  no Localhost
Adriano P. Araujo
Adriano P. Araujo

Posted on

Como usar o Docker no seu Contrato Solidity do Hardhat  no Localhost

Como fazer seu contrato de Solidity na Web3 com o Hardhat e criar uma imagem Docker com a implementação local — Passo a passo completo

Containerize seu contrato de localhost de solidity Hardhat

O que estamos construindo

Este é um passo a passo completo, compreendendo erros, explicando diferentes etapas e estratégias, sobre como vamos criar uma imagem Docker que contém e executa um contrato de Solidity Hardhat no localhost.

Eu vou aprofundar o máximo possível neste artigo, para que você entenda perfeitamente as diferentes etapas, mas também não pularei os cenários de erro para que você entenda a melhor maneira de lidar com eles.

TL; DR

Se você quiser ver o código completo, dê uma olhada na última parte para ver o Código final.


Por que fazer isso?

O Docker é ótimo para facilitar o funcionamento das coisas em várias plataformas e tornar as coisas consistentes para todos os desenvolvedores. Também é uma ótima maneira de fazer com que um aplicativo completo seja executado localmente sem ter que gastar tempo configurando ou padronizando as coisas.

A principal inspiração para isso foi um Issue do Git na Developer DAO que alguns dos desenvolvedores estavam enfrentando ao lidar com os limites da API da Etherscan e potencialmente tendo seus testes automatizados falhando por causa disso. Um contrato implantado localmente que poderia ser executado em um container Docker sem precisar de ter todo o código no repositório principal parece ser a solução mais lógica.

https://github.com/Developer-DAO/developer-dao/issues/92


Configuração do projeto

Usaremos o padrão de Projeto de amostra do Hardhat para agilizar um pouco o processo e podermos nos concentrar mais na criação da imagem do Docker versus escrever o contrato.


#!/bin/bash

npx hardhat;

# ✔ What do you want to do? · Create a basic sample project

# ✔ Hardhat project root: · /path/to/hardhat-docker

# ✔ Do you want to add a .gitignore? (Y/n) · y

# ✔ Do you want to install this sample project's dependencies with npm (hardhat [@nomiclabs/hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle) ethereum-waffle chai [@nomiclabs/hardhat-ethers](http://twitter.com/nomiclabs/hardhat-ethers) ethers)? (Y/n) · y



Enter fullscreen mode Exit fullscreen mode

Certifique-se de iniciar o git no seu IDE.


#!/bin / bash

git init

Enter fullscreen mode Exit fullscreen mode

Em seguida, faremos uma cópia do nosso arquivo hardhat.config.js para que possamos ter várias configurações baseadas, para que possamos decidir se queremos implementar diretamente na rede principal ou apenas para nossa rede local. Isso também torna as coisas um pouco mais limpas para caso queiramos usar o arquivo apenas para testes posteriores, não tendo funções em nossa configuração de produção em comparação com a configuração local.


#!/bin / bash

cp hardhat.config.js hardhat.config.local.js;

Enter fullscreen mode Exit fullscreen mode

Em seguida, temos de modificar a rede Hardhat por causa de um Problema de chainId da MetaMask:

Arquivo: hardhat.config.local.js


require ("[@nomiclabs / hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle)");

// Esta é uma amostra da tarefa Hardhat. Para aprender a criar 

// a sua própria, vá para

// [https://hardhat.org/guides/create-task.html](https://hardhat.org/guides/create-task.html)

task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {

 const accounts = await hre.ethers.getSigners();

for (const account of accounts) {

   console.log(account.address);

 }

});

// Você precisa exportar um objeto para configurar seu arquivo 

// de configuração

// Vá para [https://hardhat.org/config/](https://hardhat.org/config/) para aprender mais

/**

* [@type](http://twitter.com/type) import ( 'hardhat / config' ).HardhatUserConfig

*/

module.exports = {

 solidity: "0.8.4",

 networks: {

  hardhat: {

     chainId: 1337

   },

 },

};

Enter fullscreen mode Exit fullscreen mode

Agora que temos nossa configuração de arquivos, vamos executar uma rede local para testar se as coisas estão funcionando usando nosso novo arquivo de configuração.


#!/bin/bash

# Terminal 1

./node_modules/.bin/hardhat --config hardhat.config.local.js node;

# Expected output

# Started HTTP and WebSocket JSON-RPC server at [http://127.0.0.1:8545/](http://127.0.0.1:8545/)

# Accounts

# ========

# Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)

# Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

# …

Enter fullscreen mode Exit fullscreen mode

Enquanto mantém este terminal vivo, abra um novo e implemente o contrato.


#!/bin/bash

# Terminal 2

./node_modules/.bin/hardhat --config hardhat.config.local.js --network localhost run scripts/sample-script.js

# Expected output

# Compiling 2 files with 0.8.4

# Compilation finished successfully

# Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Enter fullscreen mode Exit fullscreen mode

Em nosso primeiro terminal, também devemos ver o seguinte resultado para mostrar que ele foi implementado com sucesso.


#!/bin/bash

# Volte ao Terminal 1

# Expected output

# eth_sendTransaction

#  Contract deployment: Greeter

#  Contract address:    0x5fbdb2315678afecb367f032d93f642f64180aa3

# ...

Enter fullscreen mode Exit fullscreen mode

Só quero fazer uma pausa aqui e dissecar rapidamente porque usei a flag --network localhost e por que isso é importante. O que às vezes acontece é que você pode encontrar o seguinte tipo de arquivo hardhat.config.js :

Arquivo ( Exemplo ) hardhat.config.js


require("[@nomiclabs/hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle)");

const pk = process.env.dapk;

module.exports = {

 solidity: "0.8.4",

 networks: {

   hardhat: {

     chainId: 1337

   },

   ropsten: {

     url: "[https://ropsten.infura.io/v3/](https://ropsten.infura.io/v3/)abcd123",

     account: [`0x${pk}`]

   },

   mainnet: {

     url: `[https://mainnet.infura.io/v3/abcd123`](https://mainnet.infura.io/v3/$%7BPROJECT_ID%7D%60),

     account: [`0x${pk}`]

   }

 }

}

Enter fullscreen mode Exit fullscreen mode

Esse pode ser um arquivo de produção típico que oferece a opção de usar várias redes. Portanto, se você deseja especificar em qual rede deseja a implementação, Geralmente usamos --network {networkname}. Se não especificarmos a rede, o Hardhat assume que o padrão é mainnet , o que não queremos. Então, no caso de nossa hardhat.condig.local.js realmente queremos garantir que isso esteja definido para --network localhost.

Ótimo, temos tudo o que precisamos para iniciar o processo de Docker para nosso contrato.

Para simplificar um pouco os comandos, vamos adicioná-los aos nossos package.json scripts.

Arquivo: package.json


{

 "name": "hardhat-docker",

 "scripts": {

  "start:local": "./node_modules/.bin/hardhat --config hardhat.config.local.js node",

   "deploy:local": "./node_modules/.bin/hardhat --config hardhat.config.local.js --network localhost run scripts/sample-script.js",

   "test:local": "./node_modules/.bin/hardhat --config hardhat.config.local.js test"

 },

 "devDependencies": {

   "[@nomiclabs/hardhat-ethers](http://twitter.com/nomiclabs/hardhat-ethers)": "^2.0.2",

   "[@nomiclabs/hardhat-waffle](http://twitter.com/nomiclabs/hardhat-waffle)": "^2.0.1",

   "chai": "^4.3.4",

   "ethereum-waffle": "^3.4.0",

   "ethers": "^5.4.7",

   "hardhat": "^2.6.4"

 }

}

Enter fullscreen mode Exit fullscreen mode

Criando arquivos e configurações do Docker

Nossa primeira iteração levará em consideração o uso da versão alpine do node e a instalação de todas as dependências.

NOTA: Eu usarei o /usr/src/app como diretório que acabei de criar, você pode tecnicamente, colocá-lo em quase qualquer lugar que quiser.

Arquivo: Dockerfile


FROM node:14-alpine

COPY . /usr/src/app

WORKDIR /usr/src/app

RUN yarn install --non-interactive --frozen-lockfile

Enter fullscreen mode Exit fullscreen mode

Vamos testar isso, mas antes disso, certifique-se de não copiar arquivos indesejados com um .dockerignore

Arquivo: .dockerignore


artifacts

cache

node_modules

test

*.log

Enter fullscreen mode Exit fullscreen mode

Com isso, vamos tentar criar nosso aplicativo, perceberemos que ele falhará com alguns erros.


#!/bin/sh

docker build . -t hhdocker;

# Expected output

# ...

# error Couldn't find the binary git

# ...

Enter fullscreen mode Exit fullscreen mode

Isso ocorre porque nossa imagem base alpine não vem com git então precisamos instalá-lo nós mesmos.

Arquivo: Dockerfile


FROM node:14-alpine

COPY . /usr/src/app

WORKDIR /usr/src/app

RUN apk add git;

RUN yarn install --non-interactive --frozen-lockfile

Enter fullscreen mode Exit fullscreen mode

Ótimo, então, quando o construirmos novamente, veremos que não haverá erros.


#!/bin/sh

docker build . -t hhdocker;

Enter fullscreen mode Exit fullscreen mode

No entanto, no momento em que o executarmos interativamente, perceberemos que nosso container do Docker não tem nada para executar especificamente, portanto, o padrão é o prompt do node.


#!/bin/sh

docker run -it --name myhd hhdocker;

# Expected output

# Welcome to Node.js v14.17.6.

# Type ".help" for more information.

# >

# Expected output after pressing ctrl + c

# >

# (To exit, press Ctrl+C again or Ctrl+D or type .exit)

# >

Enter fullscreen mode Exit fullscreen mode

Nosso principal objetivo é tentar obter yarn start:local para executar, mas também mantenha nosso ambiente de nó vivo se for executado em segundo plano e de maneira não interativa.

Para fazer isso, precisamos adicionar um entrypoint.sh arquivo que será executado no início da instância do Docker em execução. Eu gosto de manter as coisas limpas, então vou criar uma nova docker pasta na raiz para manter as coisas separadas do nosso código principal.

Arquivo: docker/entrypoint.sh


#!/bin/sh

# Change to the correct directory

cd /usr/src/app;

# Run hardhat

yarn start:local;

# Keep node alive

set -e

if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ]; then

 set -- node "$@"

fi

exec "$@"

Enter fullscreen mode Exit fullscreen mode

Precisamos então amarrar esse novo script de shell em nosso Dockerfile.

Arquivo: Dockerfile


FROM node:14-alpine

COPY . /usr/src/app

WORKDIR /usr/src/app

RUN apk add git;

RUN yarn install --non-interactive --frozen-lockfile

COPY $PWD/docker/entrypoint.sh /usr/local/bin

ENTRYPOINT ["/bin/sh", "/usr/local/bin/entrypoint.sh"]

Enter fullscreen mode Exit fullscreen mode

Vamos tentar construir e rodar nosso contêiner novamente.


#!/bin/sh

# Terminal 1

# make sure the docker container is removed with

# docker rm -f myhd;

docker build . -t hhdocker;

# ...

docker run -it --name myhd hhdocker;

# Expected output

# ./node_modules/.bin/hardhat --config hardhat.config.local.js node

# Started HTTP and WebSocket JSON-RPC server at [http://0.0.0.0:8545/](http://0.0.0.0:8545/)

# Accounts

# ========

# Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)

# Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

# …
Enter fullscreen mode Exit fullscreen mode

Parece que as coisas estão funcionando, então vamos abrir um segundo terminal e executar nosso script de implantação, mas desta vez de fora do contêiner.


#!/bin/sh

# Terminal 2

docker exec -it myhd /bin/sh -c "cd /usr/src/app; yarn deploy:local";

# Expected output

# docker exec -it myhd /bin/sh -c "cd /usr/src/app; yarn deploy:local";

# $ ./node_modules/.bin/hardhat --config hardhat.config.local.js --network localhost run scripts/sample-script.js

# Downloading compiler 0.8.4

# Compiling 2 files with 0.8.4

# Compilation finished successfully

# Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Enter fullscreen mode Exit fullscreen mode

Bom, agora podemos executar nosso nó e implantar tudo dentro do container Docker.

Testando o localhost

Em seguida, quero criar um script que teste novamente o container Docker como cliente para garantir que as coisas estejam funcionando corretamente. Você pode perguntar, e o teste padrão existente no Hardhat?

Bem, se executá-los no container Docker, você deve ver que o container não possui os testes porque não os incluímos no .dockerignore.


docker exec -it myhd /bin/sh -c "cd /usr/src/app; yarn test:local";

# Expected output

# $ ./node_modules/.bin/hardhat --config hardhat.config.local.js test

# 0 passing (2ms)

# Done in 1.52s.

Enter fullscreen mode Exit fullscreen mode

Mas se executarmos os testes fora do container Docker, veremos que todos passam.


#!/bin/sh

yarn test:local

# Greeter

# Deploying a Greeter with greeting: Hello, world!

# Changing greeting from 'Hello, world!' to 'Hola, mundo!'

#     ✓ Should return the new greeting once it's changed (618ms)

# 1 passing (623ms)

# ✨  Done in 1.84s.

Enter fullscreen mode Exit fullscreen mode

Mas esse não é um teste justo, porque você verá que, no teste, ele implementa o contrato por si só e usa esse endereço para testar, e não aquele que implantamos dentro do container. Por esse motivo, precisamos criar um script que se comunique com nosso container como cliente.

Antes de continuarmos, precisamos apenas garantir que executamos uma nova instância do container com a porta 8545 exposta.


#!/bin/sh

docker rm -f myhd;

# Run non interactive with -d and exposing the port with -p

docker run -it -d -p 8545:8545 --name myhd hhdocker;

# Verify up and running

docker logs myhd;

# Expected output (if you don't see this run above again)

# Started HTTP and WebSocket JSON-RPC server at [http://0.0.0.0:8545/](http://0.0.0.0:8545/)

# Accounts

# ========

# Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)

# Private Key: # 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

# Deploy contact

docker exec -it myhd /bin/sh -c "cd /usr/src/app; yarn deploy:local";

# Expected output

# Downloading compiler 0.8.4

# Compiling 2 files with 0.8.4

# Compilation finished successfully

# Greeter deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

# Done in 12.17s.

Enter fullscreen mode Exit fullscreen mode

Criando nosso cliente

Para o nosso cliente, considerando que estamos usando o nó, vamos criar uma pasta chamada client e adicione node.js para isso. O objetivo disso é fazer com que nosso cliente, como cliente de back-end, interaja com nosso contrato.

Arquivo: client/node.js


// Importações

const ethers = require('ethers');

const abi = require('../artifacts/contracts/Greeter.sol/Greeter.json').abi;

// Constantes - observe o endereço do contrato que implementamos acima

const CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3';

// Configuração

const provider = ethers.getDefaultProvider('[http://localhost:8545'](about:blank));

const contract = new ethers.ethers.Contract(CONTRACT_ADDRESS, abi, provider);

// função Init - async porque precisamos aguardar as funções do contrato

const init = async () => {

 try {

   const result = await contract.greet();

   console.log({ result })

 } catch (error) {

   console.log({ error });

 }

}

init();

Enter fullscreen mode Exit fullscreen mode

Agora, com nosso novo container Docker tendo suas portas expostas, vamos tentar nos conectar a ele.


#!/bin/sh

node client/node.js;

# Expected output

# { result: 'Hello, Hardhat!' }

Enter fullscreen mode Exit fullscreen mode

Vamos modificar nosso script para atualizar a mensagem e gerar uma saída mais atualizada.

Arquivo: client/node.js


// Importações

const ethers = require('ethers');

const abi = require('../artifacts/contracts/Greeter.sol/Greeter.json').abi;

//Constantes - observe o endereço do contrato que implementamos acima

const CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3';

// Configuração

const provider = ethers.getDefaultProvider('[http://localhost:8545'](about:blank));

const contract = new ethers.ethers.Contract(CONTRACT_ADDRESS, abi, provider);

// função Init - async porque precisamos aguardar as funções do contrato

const init = async () => {

 try {

   const result = await contract.greet();

   console.log({ result });

   const transaction = await contract.setGreeting('Hello from docker!');

   console.log({ transaction });

   // Espera a transação estar completa

   transaction.wait();

   // Resultado

   console.log({ result: await contract.greet() });

 } catch (error) {

   console.log({ error });

 }

}

init();

Enter fullscreen mode Exit fullscreen mode

Vamos tentar executá-lo e ver os resultados.


#!/bin/sh

node client/node.js;

# Expected output

# { result: 'Hello, Hardhat!' }

# {

#   error: Error: sending a transaction requires a signer (operation="sendTransaction", code=UNSUPPORTED_OPERATION, version=contracts/5.4.1)

# …

Enter fullscreen mode Exit fullscreen mode

Recebemos esse erro porque precisamos de uma carteira que confirme a mineração da transação assinando-a com o endereço. Se você notou quando iniciamos nosso node, ele emitiu um monte de endereços de carteira e chaves privadas. Usaremos um desses para assinar efetivamente a transação, mas, para fazer isso, precisamos modificar nosso código para permitir a assinatura.

Arquivo: client/node.js

// Importações

const ethers = require('ethers');

const abi = require('../artifacts/contracts/Greeter.sol/Greeter.json').abi;

// Constants

const CONTRACT_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3';

// Configuração

# Observe como não usamos mais [http://localhost:8545](http://localhost:8545/)

# Isso ocorre porque o JsonRpcProvider é o 

#provedor padrão de const = new #ethers.ethers.providers.JsonRpcProvider ( );

# Nosso assinante se torna o provedor

# com o endereço produzido a partir do lançamento do node

const signer = provider.getSigner('0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266')

const contract = new ethers.ethers.Contract(CONTRACT_ADDRESS, abi, signer);

// Inicialização

const init = async () => {

 try {

   const result = await contract.greet();

   console.log({ result });

const transaction = await contract.setGreeting('Hello from docker!');

   console.log({ transaction });

   // Espera a transação estar completa

   transaction.wait();

   // Resultado

   console.log({ result: await contract.greet() });

 } catch (error) {

   console.log({ error });

 }

}init();
Enter fullscreen mode Exit fullscreen mode

Agora, com nosso código atualizado, vamos tentar novamente.


node client/node.js;

# Expected output

# { result: 'Hello, Hardhat!' }

# {

#   transaction: {

#     hash: # '0x21af2b84b832d9913e30ccbcd2b9cfb4a11f897d9f0a9ef6e51d646e4411edd2'

# ...

# { result: 'Hello from docker!' }
Enter fullscreen mode Exit fullscreen mode

Ótimo, temos nosso Docker funcionando, nosso script está trabalhando com um provedor, o que vem a seguir?

Automatizando endereços de contrato e carteira

Digitar todos esses endereços é bom se você estiver testando as coisas manualmente, mas queremos automatizar completamente esse processo, sem copiar e colar.

Para tirar proveito disso, precisamos instalar dotenv para que possamos usar variáveis de ambiente em nossos arquivos.

yarn add -D dotenv;

Vamos criar um modelo para nossas variáveis de ambiente. Isso é apenas para que outras pessoas possam ter uma referência sobre o que pode ser necessário para variáveis de ambiente.

Arquivo: .env.example

CONTRACT_ADDRESS =

WALLET_ADDRESS =
Enter fullscreen mode Exit fullscreen mode

Dessa forma, quando copiamos esse arquivo como .env nós temos uma base para trabalhar.

Vamos também garantir que o nosso .dockerignore inclui o .env, não precisaremos disso no container do Docker.

Faça uma cópia do seu .env.example como .env e substitua-o pelos valores que temos em nosso arquivo node.js do cliente.

Arquivo: .env

CONTRACT_ADDRESS = 0x5FbDB2315678afecb367f032d93F642f64180aa3

WALLET_ADDRESS = 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
Enter fullscreen mode Exit fullscreen mode

Em seguida, precisaremos modificar nosso cliente com as variáveis de ambiente.

Arquivo: client/node.js

// Importações

const ethers = require('ethers');

const abi = require('../artifacts/contracts/Greeter.sol/Greeter.json').abi;

const config = require('dotenv').config;

// Configuração

const provider = new ethers.ethers.providers.JsonRpcProvider();

const signer = provider.getSigner(process.env.WALLET_ADDRESS || 'UNKNOWN_WALLET_ADDRESS');

const contract = new ethers.ethers.Contract(process.env.CONTRACT_ADDRESS || 'UNKNOWN_CONTRACT_ADDRESS', abi, signer);

// Inicialização

const init = async () => {

 try {

   const result = await contract.greet();

   console.log({ result });

   const transaction = await contract.setGreeting('Hello from docker!');

   console.log({ transaction });

   // Espera a transação estar completa

   transaction.wait();

   // Resultado

   console.log({ result: await contract.greet() });

 } catch (error) {

   console.log({ error });

 }

}

init();
Enter fullscreen mode Exit fullscreen mode

Agora que temos o script trabalhando com o .env vamos automatizar o processo de preenchimento do arquivo .env. Para fazer isso, precisamos emitir o endereço da carteira no tempo de execução do node e também o contrato implementado. A única parte infeliz é que não podemos emitir a chave secreta ( ou pelo menos não encontrei uma maneira de usar isso no Hardhat ). Pode haver a possibilidade de gerar o segredo usando alguns comandos de sed, mas tentarei manter isso um pouco mais fácil neste momento.

Para obter um contrato e um endereço de carteira, vamos usar tasks Hardhat, e criar um novo script em nosso package.json.

Vamos abrir o arquivo de configuração e modificar a tarefa existente lá.

Arquivo: hardhat.config.local.js


const fs = require('fs');

// Esta é uma tarefa de amostra do HardHat. Para aprender a criar o seu, acesse

// [https://hardhat.org/guides/create-task.html](https://hardhat.org/guides/create-task.html)

task("my-deploy", "Deploys contract, get wallets, and outputs files", async (taskArgs, hre) => {

 // Recebemos o contrato para implatá-lo

 const Greeter = await hre.ethers.getContractFactory("Greeter");

 const greeter = await Greeter.deploy("Hello, Hardhat!");

 // Espera o deploy

 await greeter.deployed();

 // Pega o endereço

 const contractAddress = greeter.address;

 // Escreve o arquivo

 fs.writeFileSync('./.contract', contractAddress);

 // Obtém as carteiras sinalizadas geradas

 const accounts = await hre.ethers.getSigners();

 // Obtém o primeiro endereço da carteira

 const walletAddress = accounts[0].address;

  // Escreve o arquibo

 fs.writeFileSync('./.wallet', walletAddress);

});

// Você precisa exportar um objeto para configurar sua //configuração

// Acess [https://hardhat.org/config/](https://hardhat.org/config/) para aprender mais

/**

* [@type](http://twitter.com/type) import('hardhat/config').HardhatUserConfig

*/

module.exports = {

 solidity: "0.8.4",

 networks: {

   hardhat: {

     chainId: 1337

   },

 },

};

Enter fullscreen mode Exit fullscreen mode

Vamos adicionar a tarefa em nosso package.json principal e usá-lo um comando de execução e facilitar as coisas para nós com o yarn.

Arquivo: package.json

{

 "scripts": {

 ...

"deploy:local": "./node_modules/.bin/hardhat --config hardhat.config.local.js --network localhost my-deploy",

 ...

 }

}
Enter fullscreen mode Exit fullscreen mode

Agora, se rodarmos o yarn deploy:local, veremos dois arquivos gerados .contract e .wallet. Também vamos adicionar estes ao .gitignore e .dockerignore porque não precisamos rastrear isso.

Arquivo: .gitignore

node_modules
.env
.contract
.wallet
#Hardhat files
cache
artifacts
Enter fullscreen mode Exit fullscreen mode

Arquivo: .dockerignore

artifacts
cache
node_modules
test
*.log
.env
.contract
.wallet
Enter fullscreen mode Exit fullscreen mode

Então, por que estamos fazendo isso de novo? O motivo é que precisamos de uma maneira de capturar esses valores para recuperá-los a partir do container do Docker. Docker solicitará esses valores dentro de si, mas não há como tirar proveito dele do lado de fora sem armazená-lo em algum lugar que possa ser acessível posteriormente. Vamos testar isso criando uma nova compilação do Docker e executando-a.

# !/bin/bash

docker build . -t hhdocker;

# Run non interactive with -d and exposing the port with -p

docker run -it -d -p 8545:8545 --name myhd hhdocker;

# Verify up and running

docker logs myhd;

# Expected output (if you don't see this run above again)

# Started HTTP and WebSocket JSON-RPC server at [http://0.0.0.0:8545/](http://0.0.0.0:8545/)

# Accounts

# ========

# Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)

# Private Key: # 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

# Deploy contact

# We'll replace our previous

# docker exec -it myhd /bin/sh -c "cd /usr/src/app; yarn deploy:local";

# with a short hand because we set our WORKDIR to /usr/src/app

# to

docker exec -it myhd yarn deploy:local;

# Expected output

# Error HH700: Artifact for contract "Greeter" not found.
Enter fullscreen mode Exit fullscreen mode

Isso ocorre porque quando adicionado ao nosso .dockerignore a pasta /artifacts não é transferida, por isso precisamos adicionar um novo script em nosso package.json que compilará apenas nosso contrato.

Arquivo: package.json

{

 "scripts": {

   ...

  "compile: local": "./node_modules/.bin/hardhat --config hardhat.config.local.js compile"

 }

 ...

}
Enter fullscreen mode Exit fullscreen mode

Agora vamos iniciar o processo de compilação novamente.

#!/bin/bash
#Remove our previous running container
docker rm -f myhd;
#Build the new container
docker build . -t hhdocker;
#Run non interactive with -d and exposing the port with -p
docker run -it -d -p 8545:8545 --name myhd hhdocker;
#Verify up and running
docker logs myhd;
#Expected output (if you don't see this run above again)
#Started HTTP and WebSocket JSON-RPC server at [http://0.0.0.0:8545/](http://0.0.0.0:8545/)
#Accounts
#========
#Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
#Private Key: #0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
#Compile contract
docker exec -it myhd yarn compile:local;
#Expected output
#Downloading compiler 0.8.4
# Compiling 2 files with 0.8.4
# Compilation finished successfully
# Done in 12.17s.
# Deploy contract
docker exec -it myhd yarn deploy:local;
# Expected output
# Done in 1.25s.
Enter fullscreen mode Exit fullscreen mode

Ótimo, mas e os arquivos que geramos? Nós podemos usar o exec comando em nossa vantagem para produzir esses resultados.

#!/bin/bash
docker exec -it myhd cat .contract;
# Expected output
# 0x5FbDB2315678afecb367f032d93F642f64180aa3
docker exec -it myhd cat .wallet;
# Expected output
# 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Enter fullscreen mode Exit fullscreen mode

Usaremos o dirty e o prettier para gerar um arquivo .env.

#!/bin/bash
# Ugly I know
echo "CONTRACT_ADDRESS=$(docker exec -it myhd cat .contract)\nWALLET_ADDRESS=$(docker exec -it myhd cat .wallet;)" > .env;
# OR you could use the local ENV
export CONTRACT_ADDRESS="$(docker exec -it myhd cat .contract)";
export WALLET_ADDRESS="$(docker exec -it myhd cat .wallet)";
echo "CONTRACT_ADDRESS=$CONTRACT_ADDRESS\nWALLET_ADDRESS=$WALLET_ADDRESS" > .env;
# Then clean up those values, don't want those living in the server
unset CONTRACT_ADDRESS;
unset WALLET_ADDRESS;
Enter fullscreen mode Exit fullscreen mode

Com isso, deveríamos ter um recém-gerado arquivo .env, com nossos endereços, que podemos usar para nosso arquivo node.js do cliente.

#!/bin/bash
node client/node.js;
# Expected result
# { result: 'Hello, Hardhat!' }
# {
# transaction: {
# hash: # '0xf59fd2a6d66dba2b24fa7b1007a6e38e4a7e42c46c15db1ade4c869792f0baee'
# ...
# { result: 'Hello from docker!' }
Enter fullscreen mode Exit fullscreen mode

Código final

Lá temos. Nosso processo semi-automático para criar um contrato pelo Docker que também recupera as chaves e executa um cliente que interage com nosso contrato no container Docker.

https://github.com/codingwithmanny/hardhat-docker

Agradecimentos Especiais

Graças a Greg Syme ( membro da Developer DAO) por sua ajuda na edição deste artigo e na escuta de minhas conversas malucas sobre contratos de Docker e Solidity.

O que vem a seguir?

O próximo passo para isso seria adicionar isso a um processo de IC com ações do GitHub, onde você pode automatizar os testes e talvez até usar o Cypress para fazer um teste E2E completo com um front-end.

Estou trabalhando nesse tutorial mais específico, então fique atento.

Se você obteve valor disso, siga-me também no twitter: @codingwithmanny e instagram @codingwithmanny.

Leitura adicional:

https://codingwithmanny.medium.com/gatsby-docker-env-vars-alternative-e81802281999

https://codingwithmanny.medium.com/automating-nodejs-ts-deployments-with-codepipeline-to-elastic-beanstalk-79664321ab91

https://codingwithmanny.medium.com/nodejs-typescript-docker-deployment-process-with-aws-ebs-14796cd78392


Este artigo foi escrito por Manny e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)