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
Certifique-se de iniciar o git no seu IDE.
#!/bin / bash
git init
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;
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
},
},
};
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
# …
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
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
# ...
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}`]
}
}
}
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"
}
}
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
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
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
# ...
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
Ótimo, então, quando o construirmos novamente, veremos que não haverá erros.
#!/bin/sh
docker build . -t hhdocker;
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)
# >
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 "$@"
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"]
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
# …
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
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.
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.
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.
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();
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!' }
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();
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)
# …
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();
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!' }
Ó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 =
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
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();
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
},
},
};
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",
...
}
}
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
Arquivo: .dockerignore
artifacts
cache
node_modules
test
*.log
.env
.contract
.wallet
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.
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"
}
...
}
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.
Ó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
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;
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!' }
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
Este artigo foi escrito por Manny e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Latest comments (0)