Saltar al contenido principal

Usando Hardhat con la C-Chain de Avalanche

Introducción

El objetivo de esta guía es establecer las mejores prácticas en cuanto a la escritura, prueba e implementación de contratos inteligentes en la C-Chain de Avalanche. Estaremos construyendo contratos inteligentes con el entorno de desarrollo Hardhat.

Requisitos previos

NodeJS y Yarn

Primero, instala la versión LTS (soporte a largo plazo) de NodeJS. Esto es 18.x en el momento de escribir esto. NodeJS incluye npm.

A continuación, instala yarn:

npm install -g yarn

AvalancheGo y Avalanche Network Runner

AvalancheGo es una implementación de nodo Avalanche escrita en Go. Avalanche Network Runner es una herramienta para desplegar rápidamente redes de prueba locales. Juntos, puedes desplegar redes de prueba locales y ejecutar pruebas en ellas.

Solidity y Avalanche

También es útil tener un entendimiento básico de Solidity y Avalanche.

Dependencias

Clona el repositorio de inicio rápido e instala los paquetes necesarios a través de yarn.

git clone https://github.com/ava-labs/avalanche-smart-contract-quickstart.git
cd avalanche-smart-contract-quickstart
yarn
info

El método de clonación del repositorio utilizado es HTTPS, pero también se puede usar SSH:

git clone [email protected]:ava-labs/avalanche-smart-contract-quickstart.git

Puedes encontrar más información sobre SSH y cómo usarlo aquí.

Escribir Contratos

Edita el contrato ExampleERC20.sol en contracts/. ExampleERC20.sol es un contrato ERC20 de Open Zeppelin. ERC20 es una interfaz de contrato inteligente popular. También puedes agregar tus propios contratos.

Configuración de Hardhat

Hardhat utiliza hardhat.config.js como archivo de configuración. Puedes definir tareas, redes, compiladores y más en ese archivo. Para más información, consulta aquí.

Aquí tienes un ejemplo de hardhat.config.ts preconfigurado.

import { task } from "hardhat/config";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { BigNumber } from "ethers";
import "@nomiclabs/hardhat-waffle";

// Al usar la red hardhat, puedes elegir bifurcar Fuji o Avalanche Mainnet
// Esto te permitirá depurar contratos usando la red hardhat mientras mantienes el estado de la red actual
// Para habilitar la bifurcación, activa uno de estos booleanos y luego ejecuta tus tareas/scripts usando ``--network hardhat``
// Para más información, consulta la guía de hardhat
// https://hardhat.org/hardhat-network/
// https://hardhat.org/guides/mainnet-forking.html
const FORK_FUJI = false;
const FORK_MAINNET = false;
const forkingData = FORK_FUJI
? {
url: "https://api.avax-test.network/ext/bc/C/rpc",
}
: FORK_MAINNET
? {
url: "https://api.avax.network/ext/bc/C/rpc",
}
: undefined;

export default {
solidity: {
compilers: [
{
version: "0.5.16",
},
{
version: "0.6.2",
},
{
version: "0.6.4",
},
{
version: "0.7.0",
},
{
version: "0.8.0",
},
],
},
networks: {
hardhat: {
gasPrice: 225000000000,
chainId: !forkingData ? 43112 : undefined, //Solo especifica un chainId si no estamos bifurcando
forking: forkingData,
},
local: {
url: "http://localhost:9650/ext/bc/C/rpc",
gasPrice: 225000000000,
chainId: 43112,
accounts: [
"0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027",
"0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07",
"0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e",
"0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc",
"0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675",
"0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff",
"0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630",
"0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60",
"0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c",
"0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a",
],
},
fuji: {
url: "https://api.avax-test.network/ext/bc/C/rpc",
gasPrice: 225000000000,
chainId: 43113,
accounts: [],
},
mainnet: {
url: "https://api.avax.network/ext/bc/C/rpc",
gasPrice: 225000000000,
chainId: 43114,
accounts: [],
},
},
};

Esta configuración proporciona la información de red necesaria para una interacción fluida con Avalanche. También hay algunas claves privadas predefinidas para pruebas en una red de prueba local.

info

El puerto en este tutorial utiliza el 9650. Dependiendo de cómo inicies tu red local, podría ser diferente.

Tareas de Hardhat

Puedes definir tareas personalizadas de hardhat en hardhat.config.ts. Hay dos tareas incluidas como ejemplos: accounts y balances.

task(
"accounts",
"Imprime la lista de cuentas",
async (args, hre): Promise<void> => {
const accounts: SignerWithAddress[] = await hre.ethers.getSigners();
accounts.forEach((account: SignerWithAddress): void => {
console.log(account.address);
});
}
);

task(
"balances",
"Imprime la lista de saldos de cuentas AVAX",
async (args, hre): Promise<void> => {
const accounts: SignerWithAddress[] = await hre.ethers.getSigners();
for (const account of accounts) {
const balance: BigNumber = await hre.ethers.provider.getBalance(
account.address
);
console.log(`${account.address} tiene un saldo de ${balance.toString()}`);
}
}
);

npx hardhat accounts imprime la lista de cuentas. npx hardhat balances imprime la lista de saldos de cuentas AVAX. Al igual que con otros scripts de yarn, puedes pasar una bandera --network a las tareas de hardhat.

Cuentas

Imprime una lista de cuentas en la red local de Avalanche Network Runner.

npx hardhat accounts --network local
0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC
0x9632a79656af553F58738B0FB750320158495942
0x55ee05dF718f1a5C1441e76190EB1a19eE2C9430
0x4Cf2eD3665F6bFA95cE6A11CFDb7A2EF5FC1C7E4
0x0B891dB1901D4875056896f28B6665083935C7A8
0x01F253bE2EBF0bd64649FA468bF7b95ca933BDe2
0x78A23300E04FB5d5D2820E23cc679738982e1fd5
0x3C7daE394BBf8e9EE1359ad14C1C47003bD06293
0x61e0B3CD93F36847Abbd5d40d6F00a8eC6f3cfFB
0x0Fa8EA536Be85F32724D57A37758761B86416123

Saldos

Imprime una lista de cuentas y sus saldos AVAX correspondientes en la red local de Avalanche Network Runner.

npx hardhat balances --network local
0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC tiene un saldo de 50000000000000000000000000
0x9632a79656af553F58738B0FB750320158495942 tiene un saldo de 0
0x55ee05dF718f1a5C1441e76190EB1a19eE2C9430 tiene un saldo de 0
0x4Cf2eD3665F6bFA95cE6A11CFDb7A2EF5FC1C7E4 tiene un saldo de 0
0x0B891dB1901D4875056896f28B6665083935C7A8 tiene un saldo de 0
0x01F253bE2EBF0bd64649FA468bF7b95ca933BDe2 tiene un saldo de 0
0x78A23300E04FB5d5D2820E23cc679738982e1fd5 tiene un saldo de 0
0x3C7daE394BBf8e9EE1359ad14C1C47003bD06293 tiene un saldo de 0
0x61e0B3CD93F36847Abbd5d40d6F00a8eC6f3cfFB tiene un saldo de 0
0x0Fa8EA536Be85F32724D57A37758761B86416123 tiene un saldo de 0

Observa que la primera cuenta ya está financiada. Esto se debe a que esta dirección está prefinanciada en el archivo de génesis de la red local.

Saldos de ERC20

task("check-erc20-balance", "Imprime el saldo ERC20 de tu cuenta").setAction(
async function (taskArguments, hre) {
const genericErc20Abi = require("./erc20.abi.json");
const tokenContractAddress = "0x...";
const provider = ethers.getDefaultProvider(
"https://api.avax.network/ext/bc/C/rpc"
);
const contract = new ethers.Contract(
tokenContractAddress,
genericErc20Abi,
provider
);
const balance = await contract.balanceOf("0x...");
console.log(`Saldo en wei: ${balance}`);
}
);

Esto devolverá el resultado en wei. Si quieres saber la cantidad exacta de tokens con su nombre de token, entonces necesitas dividirlo por su decimal. erc20.abi.json se puede encontrar aquí.

El ejemplo utiliza la API pública de la C-Chain para el proveedor. Para una red Avalanche local, usa http://127.0.0.1:9650/ext/bc/C/rpc y para la Testnet Fuji, usa https://api.avax-test.network/ext/bc/C/rpc.

Ayuda de Hardhat

Ejecuta yarn hardhat para listar la versión de Hardhat, las instrucciones de uso, las opciones globales y las tareas disponibles.

Flujo de trabajo típico con Avalanche Network Runner

Ejecutar Avalanche Network Runner

Primero, confirma que tienes la última versión de AvalancheGo construida.

cd /ruta/a/avalanchego
git fetch -p
git checkout master
./scripts/build.sh

(Ten en cuenta que también puedes descargar binarios precompilados de AvalancheGo en lugar de construir desde el código fuente).

Confirma que tienes Avalanche Network Runner instalado siguiendo los pasos enumerados aquí.

Inicia Avalanche Network Runner y ejecuta un script para iniciar una nueva red local.

Iniciar el servidor

$ cd /ruta/a/Avalanche-Network-Runner
$ avalanche-network-runner server \
--log-level debug \
--port=":8080" \
--grpc-gateway-port=":8081"

Iniciar una nueva red Avalanche con cinco nodos

# reemplaza execPath con la ruta a AvalancheGo en tu máquina
# por ejemplo, ${HOME}/go/src/github.com/ava-labs/avalanchego/build/avalanchego
$ AVALANCHEGO_EXEC_PATH="avalanchego"
$ avalanche-network-runner control start \
--log-level debug \
--endpoint="0.0.0.0:8080" \
--number-of-nodes=5 \
--avalanchego-path ${AVALANCHEGO_EXEC_PATH}

Ahora estás ejecutando una red Avalanche local con 5 nodos.

Financiar cuentas

Transfiere 1,000 AVAX desde la X-Chain a cada una de las 10 cuentas en hardhat.config.ts con el script fund-cchain-addresses. Financiar estas cuentas es un requisito previo para desplegar e interactuar con contratos inteligentes.

Nota: Si ves Error: Invalid JSON RPC response: "API call rejected because chain is not done bootstrapping", debes esperar hasta que la red se haya iniciado y esté lista para usar. No debería tomar mucho tiempo.

> const [account1, account2] = await ethers.getSigners();
undefined
> account1.address
'0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC'
> account2.address
'0x9632a79656af553F58738B0FB750320158495942'

Check the balance of account1:

> const balance1 = await coin.balanceOf(account1.address);
undefined
> balance1.toString()
'500000000000000000000000000'

Transfer some tokens from account1 to account2:

> await coin.transfer(account2.address, ethers.utils.parseEther('100'));
{
hash: '0x...',
...
}

Check the updated balances:

> const newBalance1 = await coin.balanceOf(account1.address);
undefined
> newBalance1.toString()
'499999999999999999999999900'
> const newBalance2 = await coin.balanceOf(account2.address);
undefined
> newBalance2.toString()
'100000000000000000000'

Exit the console:

> .exit

Congratulations! You have successfully deployed and interacted with a smart contract on the Avalanche network.

> let accounts = await ethers.provider.listAccounts()
undefined
> accounts
[
'0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC',
'0x9632a79656af553F58738B0FB750320158495942',
'0x55ee05dF718f1a5C1441e76190EB1a19eE2C9430',
'0x4Cf2eD3665F6bFA95cE6A11CFDb7A2EF5FC1C7E4',
'0x0B891dB1901D4875056896f28B6665083935C7A8',
'0x01F253bE2EBF0bd64649FA468bF7b95ca933BDe2',
'0x78A23300E04FB5d5D2820E23cc679738982e1fd5',
'0x3C7daE394BBf8e9EE1359ad14C1C47003bD06293',
'0x61e0B3CD93F36847Abbd5d40d6F00a8eC6f3cfFB',
'0x0Fa8EA536Be85F32724D57A37758761B86416123'
]

Esta es exactamente la misma lista de cuentas que en yarn accounts.

Ahora podemos interactuar con nuestro contrato ERC-20:

> let value = await coin.balanceOf(accounts[0])
undefined
> value.toString()
'123456789'
> value = await coin.balanceOf(accounts[1])
BigNumber { _hex: '0x00', _isBigNumber: true }
> value.toString()
'0'

La cuenta[0] tiene un saldo porque la cuenta[0] es la cuenta predeterminada. El contrato se despliega con esta cuenta. El constructor de ERC20.sol acuña TOTAL_SUPPLY de 123456789 tokens para el desplegador del contrato.

La cuenta[1] actualmente no tiene saldo. Enviemos algunos tokens a cuenta[1], que es 0x9632a79656af553F58738B0FB750320158495942.

> let result = await coin.transfer(accounts[1], 100)
undefined
> result
{
hash: '0x35eec91011f9089ba7689479617a90baaf8590395b5c80bb209fa7000e4848a5',
type: 0,
accessList: null,
blockHash: null,
blockNumber: null,
transactionIndex: null,
confirmations: 0,
from: '0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC',
gasPrice: BigNumber { _hex: '0x34630b8a00', _isBigNumber: true },
gasLimit: BigNumber { _hex: '0x8754', _isBigNumber: true },
to: '0x17aB05351fC94a1a67Bf3f56DdbB941aE6c63E25',
value: BigNumber { _hex: '0x00', _isBigNumber: true },
nonce: 3,
data: '0xa9059cbb0000000000000000000000009632a79656af553f58738b0fb7503201584959420000000000000000000000000000000000000000000000000000000000000064',
r: '0xc2b9680771c092a106eadb2887e5bff41fcda166c8e00f36ae79b196bbc53d36',
s: '0x355138cb5e2b9f20c15626638750775cfc9423881db374d732a8549d05ebf601',
v: 86260,
creates: null,
chainId: 43112,
wait: [Function (anonymous)]
}

Nota: Como esta es una red local, no necesitamos esperar hasta que la transacción sea aceptada. Sin embargo, para otras redes como fuji o mainnet, debes esperar hasta que la transacción sea aceptada con: await result.wait().

Ahora podemos asegurarnos de que los tokens se hayan transferido:

> value = await coin.balanceOf(accounts[0])
BigNumber { _hex: '0x075bccb1', _isBigNumber: true }
> value.toString()
'123456689'
> value = await coin.balanceOf(accounts[1])
BigNumber { _hex: '0x64', _isBigNumber: true }
> value.toString()
'100'

Como habrás notado, no había información de "remisor" en await coin.transfer(accounts[1], 100); esto se debe a que ethers utiliza el primer firmante como el firmante predeterminado. En nuestro caso, esto es cuenta[0]. Si queremos usar otra cuenta, necesitamos conectarnos con ella primero.

> let signer1 = await ethers.provider.getSigner(1)
> let contractAsSigner1 = coin.connect(signer1)

Ahora podemos llamar al contrato con signer1, que es cuenta[1].

> await contractAsSigner1.transfer(accounts[0], 5)
{
hash: '0x807947f1c40bb723ac312739d238b62764ae3c3387c6cdbbb6534501577382dd',
type: 0,
accessList: null,
blockHash: null,
blockNumber: null,
transactionIndex: null,
confirmations: 0,
from: '0x9632a79656af553F58738B0FB750320158495942',
gasPrice: BigNumber { _hex: '0x34630b8a00', _isBigNumber: true },
gasLimit: BigNumber { _hex: '0x8754', _isBigNumber: true },
to: '0x17aB05351fC94a1a67Bf3f56DdbB941aE6c63E25',
value: BigNumber { _hex: '0x00', _isBigNumber: true },
nonce: 2,
data: '0xa9059cbb0000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fc0000000000000000000000000000000000000000000000000000000000000005',
r: '0xcbf126dd0b109491d037c5f3af754ef2d0d7d06149082b13d0e27e502d3adc5b',
s: '0x5978521804dd15674147cc6b532b8801c4d3a0e94f41f5d7ffaced14b9262504',
v: 86259,
creates: null,
chainId: 43112,
wait: [Function (anonymous)]
}

Verifiquemos los saldos ahora:

> value = await coin.balanceOf(accounts[0])
BigNumber { _hex: '0x075bccb6', _isBigNumber: true }
> value.toString()
'123456694'
> value = await coin.balanceOf(accounts[1])
BigNumber { _hex: '0x5f', _isBigNumber: true }
> value.toString()
'95'

Hemos transferido con éxito 5 tokens de cuenta[1] a cuenta[0]

Resumen

Ahora tienes las herramientas que necesitas para lanzar una red Avalanche local, crear un proyecto Hardhat, así como crear, compilar, desplegar e interactuar con contratos Solidity.

Únete a nuestro Servidor de Discord para aprender más y hacer cualquier pregunta que puedas tener.

Was this page helpful?