Проект PetShop, день 1: Создание простого токена с помощью Hardhat

Я планирую создать простое web3-приложение для зоомагазина с нуля. Каждый день я буду пытаться улучшить приложение и записывать некоторые заметки. Надеюсь, что таким образом я смогу узнать больше о Ethereum, Solidity, Hardhat и разработке web3.

Проект размещен на GitHub: https://github.com/zhengzhong/petshop.

Цели первого дня

  • Создать проект Hardhat
  • Создать и скомпилировать простой токен в Solidity
  • Создать и запустить несколько тестов для этого контракта
  • Развернуть контракт в локальной сети Hardhat
  • Развернуть контракт в тестовой сети Goerli.

Настройка проекта Hardhat

Я в основном следовал учебнику Hardhat для начинающих. Я использовал node v16.9.1 и npm 8.7.0. Все шло гладко. Поэтому ниже я кратко излагаю шаги без подробных объяснений.

Во-первых, создайте проект npm, установите некоторые зависимости и создайте пустой файл hardhat.config.js:

$ npm init
...

$ npm install --save-dev 
    hardhat 
    @nomicfoundation/hardhat-toolbox 
    @nomiclabs/hardhat-ethers 
    ethers 
    dotenv
...

$ npx hardhat
888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888
👷 Welcome to Hardhat v2.9.9 👷‍
? What do you want to do? …
  Create a JavaScript project
  Create a TypeScript project
❯ Create an empty hardhat.config.js
  Quit
Войдите в полноэкранный режим Выйти из полноэкранного режима

Создайте простой токен

Следуя конвенции Hardhat, я буду использовать следующие директории:

Создайте contracts/SimpleToken.sol, который реализует простой (неERC20) токен. Код контракта скопирован и слегка изменен из учебника Hardhat.

// SPDX-License-Identifier: UNLICENSED

// Solidity files have to start with this pragma.
// It will be used by the Solidity compiler to validate its version.
pragma solidity ^0.8.16;

import "hardhat/console.sol";

// This is the main building block for smart contracts.
contract SimpleToken {
    // Some string type variables to identify the token.
    string public name = "My Simple Token";
    string public symbol = "MST";

    // The fixed amount of tokens, stored in an unsigned integer type variable.
    uint256 public totalSupply = 1_000_000;

    // An address type variable is used to store ethereum accounts.
    address public owner;

    // A mapping is a key/value map. Here we store each account's balance.
    mapping(address => uint256) balances;

    // The Transfer event is emitted when someone transfers some token(s) to someone else.
    // The event helps off-chain applications understand what happens within the contract.
    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    /**
     * Contract initialization.
     */
    constructor() {
        // The totalSupply is assigned to the transaction sender, which is the
        // account that is deploying the contract.
        balances[msg.sender] = totalSupply;
        owner = msg.sender;
    }

    /**
     * A function to transfer tokens.
     *
     * The `external` modifier makes a function *only* callable from *outside*
     * the contract.
     */
    function transfer(address to, uint256 amount) external {
        // Check if the transaction sender has enough tokens.
        require(balances[msg.sender] >= amount, "Not enough tokens");
        if (msg.sender != to) {
            console.log("Transferring %s tokens: %s => %s", amount, msg.sender, to);
            balances[msg.sender] -= amount;
            balances[to] += amount;
            // Notify off-chain applications of the transfer.
            emit Transfer(msg.sender, to, amount);
        }
    }

    /**
     * Read only function to retrieve the token balance of a given account.
     *
     * The `view` modifier indicates that it doesn't modify the contract's
     * state, which allows us to call it without executing a transaction.
     */
    function balanceOf(address account) external view returns (uint256) {
        console.log("Querying balance of %s: %s", account, balances[account]);
        return balances[account];
    }
}
Вход в полноэкранный режим Выйдите из полноэкранного режима

Скомпилируйте контракт:

$ npx hardhat compile
...
Compiled 2 Solidity files successfully
Войти в полноэкранный режим Выйти из полноэкранного режима

При успешной компиляции Hardhat сгенерирует артефакт, включая ABI контракта и его байткод, в директории artifacts/.

Тестирование простого токена

Мы используем ethers.js для взаимодействия с контрактом, который мы создали и развернули на Ethereum. В качестве бегуна для тестирования мы используем Mocha.

Перед написанием теста обновите hardhat.config.js, чтобы импортировать плагин hardhat-toolbox. Этот плагин собирает все часто используемые пакеты и плагины Hardhat, включая ethers.js, который нужен нам в нашем тесте.

require("@nomicfoundation/hardhat-toolbox");
Вход в полноэкранный режим Выйдите из полноэкранного режима

Затем создайте test/SimpleToken.js (этот файл, опять же, скопирован и слегка изменен из учебника Hardhat):

// This is an example test file. Hardhat will run every *.js file in `test/`,
// so feel free to add new ones.

// Hardhat tests are normally written with Mocha and Chai.

// Optional: `ethers` is injected in global scope automatically.
// TODO: Is this because of `require("@nomicfoundation/hardhat-toolbox")` in `hardhat.config.js`?
const { ethers } = require("hardhat");

// We import Chai to use its asserting functions here.
// See also: https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-chai-matchers
const { expect } = require("chai");

// We use `loadFixture` to share common setups (or fixtures) between tests.
// Using this simplifies your tests and makes them run faster, by taking
// advantage of Hardhat Network's snapshot functionality.
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

// `describe` is a Mocha function that allows you to organize your tests.
// Having your tests organized makes debugging them easier. All Mocha
// functions are available in the global scope.
//
// `describe` receives the name of a section of your test suite, and a
// callback. The callback must define the tests of that section. This callback
// can't be an async function.
describe("SimpleToken contract", function () {

  // A fixture is a setup function that is run only the first time it's invoked.
  // On subsequent invocations, instead of re-running it, Hardhat will reset the state
  // of the network to what it was at the point after the fixture was initially executed.
  async function deploySimpleTokenFixture() {
    const SimpleToken = await ethers.getContractFactory("SimpleToken");
    const [owner, addr1, addr2] = await ethers.getSigners();

    // To deploy our contract, we just have to call Token.deploy() and await
    // its deployed() method, which happens onces its transaction has been mined.
    const simpleToken = await SimpleToken.deploy();
    await simpleToken.deployed();

    // Fixtures can return anything you consider useful for your tests.
    return { SimpleToken, simpleToken, owner, addr1, addr2 };
  }


  // You can nest describe calls to create subsections.
  describe("Deployment", function() {
    // `it` is another Mocha function. This is the one you use to define each
    // of your tests. It receives the test name, and a callback function.
    // If the callback function is async, Mocha will `await` it.
    it("should set the right owner", async function() {
      const { simpleToken, owner } = await loadFixture(deploySimpleTokenFixture);
      expect(await simpleToken.owner()).to.equal(owner.address);
    });

    it("should assign the total supply of tokens to the owner", async function () {
      const { simpleToken, owner } = await loadFixture(deploySimpleTokenFixture);
      const ownerBalance = await simpleToken.balanceOf(owner.address);
      expect(await simpleToken.totalSupply()).to.equal(ownerBalance);
    });

    it("should initialize the contract correctly", async function () {
      const { simpleToken } = await loadFixture(deploySimpleTokenFixture);
      expect(await simpleToken.name()).to.equal("My Simple Token");
      expect(await simpleToken.symbol()).to.equal("MST");
      expect(await simpleToken.totalSupply()).to.equal(1000000);
    });
  });


  describe("Transactions", function() {

    it("should transfer tokens between accounts", async function() {
      const { simpleToken, owner, addr1, addr2 } = await loadFixture(deploySimpleTokenFixture);

      expect(await simpleToken.balanceOf(addr1.address)).to.equal(0);
      expect(await simpleToken.balanceOf(addr2.address)).to.equal(0);

      // Transfer 50 tokens from owner to addr1.
      await expect(simpleToken.transfer(addr1.address, 50))
        .to.changeTokenBalances(simpleToken, [owner, addr1], [-50, 50]);

      // Transfer 50 tokens from addr1 to addr2.
      await expect(simpleToken.connect(addr1).transfer(addr2.address, 50))
        .to.changeTokenBalances(simpleToken, [addr1, addr2], [-50, +50]);
    });

    it("should emit Transfer events", async function () {
      const { simpleToken, owner, addr1, addr2 } = await loadFixture(deploySimpleTokenFixture);

      // Transfer 50 tokens from owner to addr1.
      await expect(simpleToken.transfer(addr1.address, 50))
        .to.emit(simpleToken, "Transfer").withArgs(owner.address, addr1.address, 50);

      // Transfer 50 tokens from addr1 to addr2.
      await expect(simpleToken.connect(addr1).transfer(addr2.address, 50))
        .to.emit(simpleToken, "Transfer").withArgs(addr1.address, addr2.address, 50);
    });

    it("should fail if sender doesn't have enough tokens", async function () {
      const { simpleToken, owner, addr1 } = await loadFixture(deploySimpleTokenFixture);
      const initialOwnerBalance = await simpleToken.balanceOf(owner.address);

      // Try to send 1 token from addr1 (0 tokens) to owner (1000 tokens).
      // `require` will evaluate false and revert the transaction.
      await expect(simpleToken.connect(addr1).transfer(owner.address, 1))
        .to.be.revertedWith("Not enough tokens");

      // Owner balance shouldn't have changed.
      expect(await simpleToken.balanceOf(owner.address)).to.equal(initialOwnerBalance);
    });

    it("should do nothing if transfer to self", async function () {
      const { simpleToken, owner } = await loadFixture(deploySimpleTokenFixture);
      await expect(simpleToken.transfer(owner.address, 1))
        .to.changeTokenBalances(simpleToken, [owner], [0]);
      await expect(simpleToken.transfer(owner.address, 1))
        .to.not.emit(simpleToken, "Transfer");
    });

  });

});
Войти в полноэкранный режим Выйдите из полноэкранного режима

Запустите тест:

$ npx hardhat test
  SimpleToken contract
    Deployment
      ✔ should set the right owner (1213ms)
      ✔ should assign the total supply of tokens to the owner (44ms)
      ✔ should initialize the contract correctly (42ms)
    Transactions
      ✔ should transfer tokens between accounts (196ms)
      ✔ should emit Transfer events (45ms)
      ✔ should fail if sender doesn't have enough tokens (60ms)
      ✔ should do nothing if transfer to self (58ms)
7 passing (2s)
Войти в полноэкранный режим Выйти из полноэкранного режима

Развертывание в локальной сети Hardhat

Теперь мы собираемся развернуть простой контракт с токенами в локальной сети Hardhat.

Создайте сценарий развертывания в scripts/deploySimpleToken.js:

async function main() {
  // According to `hardhat.config.js` and the network we use,
  // this will give us an array of accounts.
  const [deployer] = await ethers.getSigners();
  console.log(`Deployer: ${deployer.address}`);
  console.log(`Deployer has a balance of: ${await deployer.getBalance()}`);

  const SimpleToken = await ethers.getContractFactory("SimpleToken");
  const simpleToken = await SimpleToken.deploy();
  await simpleToken.deployed();
  console.log(`Deployed SimpleToken at: ${simpleToken.address}`);
  console.log(`Deployer now has a balance of: ${await deployer.getBalance()}`);

  console.log(`Current block number: ${await ethers.provider.getBlockNumber()}`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
Войдите в полноэкранный режим Выход из полноэкранного режима

Сначала попробуйте развернуть контракт в локальной сети Hardhat. Для этого мы вызываем скрипт без указания аргумента --network:

$ npx hardhat run scripts/deploySimpleToken.js
...
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployer has a balance of: 10000000000000000000000
Deployed SimpleToken at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Deployer now has a balance of: 9999998536390000000000
Current block number: 1
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь наш первый контракт развернут, и мы получили адрес контракта. Попробуйте написать другой сценарий для взаимодействия с контрактом.

// Load the compiled contract artifact, following Hardhat's project layout.
const CONTRACT_ARTIFACT = require("../artifacts/contracts/SimpleToken.sol/SimpleToken.json");

// NOTE: We hard-code the address at which the contract is deployed.
// Depending on the network, make sure it has the correct value.
const CONTRACT_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3";

async function showBalances(accounts, tokenContract) {
  const formatRow = (cells, padChar) => {
    const paddings = [10, 42, 25, 10];
    return cells
      .map((cell, i) => `${cell}`.padEnd(paddings[i], padChar || ' '))
      .join(' | ');
  };

  const headings = ['Name', 'Address', 'ETH', 'Token (MST)'];
  console.log(formatRow(headings));
  console.log(formatRow(['', '', '', ''], '-'));
  for (const name of Object.keys(accounts)) {
    const account = accounts[name];
    const wei = await ethers.provider.getBalance(account.address);
    const eth = ethers.utils.formatEther(wei);
    const mst = await tokenContract.balanceOf(account.address);
    const cells = [name, account.address, eth, mst];
    console.log(formatRow(cells));
  }
  console.log(formatRow(['', '', '', ''], '-'));
}

async function measureTime(tag, asyncFn) {
  const startTime = new Date();
  const result = await asyncFn();
  const endTime = new Date();
  const seconds = (endTime - startTime) / 1000.0;
  console.log(`    * ${tag} - Used ${seconds.toFixed(2)} seconds`);
  return result;
}


async function transferTokens(from, to, amount, contract) {
  // Create and send a transaction to transfer tokens. Return a promise of `TransactionResponse`
  // which is published on the network but not necessarily mined.
  console.log(`Transferring ${amount} MST from ${from.address} to ${to.address}...`);
  const tx = await measureTime(
    'Creating tx',
    async () => contract.connect(from).transfer(to.address, amount)
  );

  // Wait for the transaction to be confirmed and included in the chain.
  console.log(`Waiting for tx ${tx.hash} to be mined...`);
  const receipt = await measureTime(
    'Waiting tx to be mined',
    () => tx.wait()
  );

  console.log(`Tx ${receipt.transactionHash} mined successfully.`);
  console.log(`    From / To     : ${receipt.from} => ${receipt.to}`);
  console.log(`    EIP-2718 Type : ${receipt.type}`);
  console.log(`    Status        : ${receipt.status}`);
  console.log(`    Block Number  : ${receipt.blockNumber}`);
  console.log(`    Block Hash    : ${receipt.blockHash}`);
  console.log(`    Gas Used      : ${receipt.gasUsed} (${receipt.effectiveGasPrice} wei / gas)`);

  // A successful transfer should emit a `Transfer` event.
  const event = receipt.events.find(event => event.event === 'Transfer');
  const [eventFrom, eventTo, eventValue] = event.args;
  console.log(`Found Transfer event: ${eventFrom} => ${eventTo}: ${eventValue}`);

  return receipt;
}

async function main() {
  console.log(`Current block number: ${await ethers.provider.getBlockNumber()}`);

  // Assume that we have at least 2 accounts.
  const [jason, orphee] = await ethers.getSigners();
  const accounts = {
    'Jason': jason,
    'Orphee': orphee,
  }

  // Construct the contract from its address and ABI.
  // NOTE: It's too hard to query the contract ABI from its address, so ABI must be provided.
  // See: https://github.com/ethers-io/ethers.js/issues/129
  const contract = new ethers.Contract(
    CONTRACT_ADDRESS,
    CONTRACT_ARTIFACT.abi,
    ethers.provider
  );
  const tokenName = await contract.name();
  const tokenSymbol = await contract.symbol();
  const owner = await contract.owner();
  console.log(`Constructed token contract: ${tokenName} (${tokenSymbol}), owner is ${owner}`);

  // Query the account balances.
  await showBalances(accounts, contract);

  // Transfer tokens from Jason to Orphée.
  console.log('Transferring tokens from Jason to Orphée...');
  await transferTokens(jason, orphee, 16, contract);
  await showBalances(accounts, contract);

  // Transfer tokens back from Jason to Orphée.
  console.log('Transferring tokens from Orphée to Jason...');
  await transferTokens(orphee, jason, 16, contract);
  await showBalances(accounts, contract);

  console.log(`Current block number: ${await ethers.provider.getBlockNumber()}`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
Войти в полноэкранный режим Выход из полноэкранного режима

Примечание: Передавать аргументы командной строки скриптам, запускаемым командой hardhat run, очень сложно, поэтому мы жестко закодируем адрес контракта внутри скрипта. Убедитесь, что константа CONTRACT_ADDRESS определена правильно.

Давайте попробуем запустить скрипт в локальной сети Hardhat:

$ npx hardhat run scripts/testSimpleToken.js
Current block number: 0
Error: call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ] (method="name()", data="0x", errorArgs=null, errorName=null, errorSignature=null, reason=null, code=CALL_EXCEPTION, version=abi/5.7.0)
    at ... {
  reason: null,
  code: 'CALL_EXCEPTION',
  method: 'name()',
  data: '0x',
  errorArgs: null,
  errorName: null,
  errorSignature: null,
  address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
  args: [],
  transaction: {
    data: '0x06fdde03',
    to: '0x5FbDB2315678afecb367f032d93F642f64180aa3'
  }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Упс! Не удалось вызвать метод контракта name(). Обратите внимание, что номер текущего блока равен 0! Это означает, что во время выполнения нашего сценария сеть пуста и контракт, с которым мы хотим взаимодействовать, НЕ развернут.

Это происходит потому, что локальная сеть Hardhat работает как узел в процессе, который запускается, когда мы вызываем команду hardhat run, и останавливается, когда команда завершается. Все на нем, включая наш контракт, не сможет пережить две команды hardhat run.

Чтобы исправить это, мы запустим сеть Hardhat как отдельный демон. Откройте новый терминал и выполните команду run:

$ npx hardhat node
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.
Account #0: 0xf39F**** (10000 ETH)
Private Key: 0xac09****
Account #1: 0x7099**** (10000 ETH)
Private Key: 0x59c6****
...
Войти в полноэкранный режим Выйти из полноэкранного режима

Продолжаем работать. Затем мы переключимся на другой терминал и снова развернем наш контракт в этой сети (с именем localhost):

$ npx hardhat run scripts/deploySimpleToken.js --network localhost
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployer has a balance of: 10000000000000000000000
Deployed SimpleToken at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Deployer now has a balance of: 9999999316982000000000
Current block number: 1
Войти в полноэкранный режим Выйдите из полноэкранного режима

Убедитесь, что константа CONTRACT_ADDRESS в нашем тестовом файле совпадает с адресом контракта. Затем запустите тест снова:

$ npx hardhat run scripts/testSimpleToken.js --network localhost
Current block number: 1
Constructed token contract: My Simple Token (MST), owner is 0xf39F****
Name    | Address    | ETH                     | Token (MST)
------- | ---------- | ----------------------- | ----------
Jason   | 0xf39F**** | 9999.999316982          | 1000000
Orphee  | 0x7099**** | 10000.0                 | 0
------- | ---------- | ----------------------- | ----------
Transferring tokens from Jason to Orphée...
Transferring 16 MST from 0xf39F****... to 0x7099****...
    * Creating tx - Used 0.09 seconds
Waiting for tx 0xcbb4**** to be mined...
    * Waiting tx to be mined - Used 0.01 seconds
Tx 0xcbb4**** mined successfully.
    From / To     : 0xf39F**** => 0x5FbD****
    EIP-2718 Type : 2
    Status        : 1
    Block Number  : 2
    Block Hash    : 0x7ab8****
    Gas Used      : 56004 (771316817 wei / gas)
Found Transfer event: 0xf39F**** => 0x7099****: 16
Name    | Address    | ETH                     | Token (MST)
------- | ---------- | ----------------------- | ----------
Jason   | 0xf39F**** | 9999.999273785172980732 | 999984
Orphee  | 0x7099**** | 10000.0                 | 16
------- | ---------- | ----------------------- | ----------
Transferring tokens from Orphée to Jason...
Transferring 16 MST from 0x7099**** to 0xf39F****...
    * Creating tx - Used 0.22 seconds
Waiting for tx 0x1c87**** to be mined...
    * Waiting tx to be mined - Used 0.01 seconds
Tx 0x1c87**** mined successfully.
    From / To     : 0x7099**** => 0x5FbD****
    EIP-2718 Type : 2
    Status        : 1
    Block Number  : 3
    Block Hash    : 0x7cc0****
    Gas Used      : 34104 (675262189 wei / gas)
Found Transfer event: 0x7099**** => 0xf39F****: 16
Name    | Address    | ETH                     | Token (MST)
------- | ---------- | ----------------------- | ----------
Jason   | 0xf39F**** | 9999.999273785172980732 | 1000000
Orphee  | 0x7099**** | 9999.999976970858306344 | 0
------- | ---------- | ----------------------- | ----------
Current block number: 3
Войти в полноэкранный режим Выйдите из полноэкранного режима

На этот раз все выглядит хорошо.

Настройка тестовой сети Goerli в Hardhat

Мы хотим развернуть этот контракт в тестовой сети Goerli. На момент написания этой статьи Goerli — это тестовая сеть, рекомендованная для разработчиков приложений. Прежде чем мы сможем это сделать, нам нужно провести некоторые подготовительные работы:

  • Экспортировать закрытые ключи некоторых тестовых аккаунтов из MetaMask
  • Создать API-ключ для Alchemy
  • Создать API-ключ для Etherscan
  • Настроить тестовую сеть Goerli в hardhat.config.js.
  • Запросите немного эфира из крана Goerli

Я установил MetaMask как плагин для Chrome. Щелкните значок плагина MetaMask. Переключите сеть на «Goerli Test Network». Убедитесь, что у нас есть как минимум 2 учетные записи в MetaMask. Если нет, создайте (или импортируйте) несколько учетных записей.

Затем экспортируйте закрытые ключи двух учетных записей из MetaMask.

Примечание: НЕ раскрывайте никому свои закрытые ключи!

Перейдите на сайт Alchemy, зарегистрируйтесь, создайте новое приложение в его приборной панели и возьмите ключ API. Alchemy предоставляет API для блокчейна и инфраструктуру узлов Ethereum. Мы будем подключаться к тестовой сети Goerli через узел (и сервер JSON-RPC, работающий на этом узле), предоставленный Alchemy.

Перейдите на сайт Etherscan, зарегистрируйтесь и создайте токен API-ключа. Etherscan — это проводник блокчейна, позволяющий просматривать контракты, транзакции и все остальное в сети Ethereum. Он также предоставляет услугу проверки исходного кода смарт-контрактов. Мы будем использовать Etherscan для верификации нашего контракта.

Теперь у нас есть 2 приватных ключа для наших аккаунтов (я назвал эти два аккаунта Jason и Orphée), ключ API Alchemy и ключ API Etherscan. Мы поместим эти секреты в файл .env, сохраним этот файл вне контроля исходного кода и воспользуемся dotenv для загрузки их в process.env. Наш файл .env выглядит следующим образом:

# My MetaMask test accounts:
#
# | Alias  | Address                                    |
# +--------+--------------------------------------------+
# | Jason  | 0xCc4c8184CC4A5A03babC13D832cEE3E41bE92d08 |
# | Orphée | 0xBf1381Fc04fe3e500e0fFD8adb985b9b8Fe95437 |
#
GOERLI_PRIVATE_KEY_JASON  = "****"
GOERLI_PRIVATE_KEY_ORPHEE = "****"
# Grabbed from: https://dashboard.alchemyapi.io/
ALCHEMY_API_KEY = "****"
# Grabbed from: https://etherscan.io/myapikey
ETHERSCAN_API_KEY = "****"
Войти в полноэкранный режим Выйти из полноэкранного режима

Обновите hardhat.config.js следующим образом:

require("@nomicfoundation/hardhat-toolbox");
require("@nomiclabs/hardhat-etherscan");

// Load secrets from `.env` file into `process.env`.
require('dotenv').config();

const {
  GOERLI_PRIVATE_KEY_JASON,
  GOERLI_PRIVATE_KEY_ORPHEE,
  ALCHEMY_API_KEY,
  ETHERSCAN_API_KEY,
} = process.env;

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.16",
  networks: {
    goerli: {
      url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
      accounts: [GOERLI_PRIVATE_KEY_JASON, GOERLI_PRIVATE_KEY_ORPHEE],
    },
  },
  etherscan: {
    apiKey: ETHERSCAN_API_KEY,
  },
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Наконец, запросите немного тестового эфира из крана Goerli. Для отправки транзакций в тестовую сеть Goerli нам понадобится немного эфира, чтобы оплатить стоимость газа.

Развертывание в тестовой сети Goerli

Теперь мы можем повторно запустить сценарий развертывания на тестовой сети Goerli:

$ npx hardhat run scripts/deploySimpleToken.js --network goerli
Deployer: 0xCc4c8184CC4A5A03babC13D832cEE3E41bE92d08
Deployer has a balance of: 736171761944897241
Deployed SimpleToken at: 0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783
Deployer now has a balance of: 736045099082540281
Current block number: 7464064
Войти в полноэкранный режим Выход из полноэкранного режима

Отлично! Теперь наш контракт находится в сети Goerli testnet, и мы получили его адрес. Мы можем просмотреть наш контракт через Etherscan.

Давайте попробуем запустить другой скрипт для перевода токенов. Обновите CONTRACT_ADDRESS в scripts/testSimpleToken.js и запустите:

$ npx hardhat run scripts/testSimpleToken.js --network goerli
Current block number: 7464081
Constructed token contract: My Simple Token (MST), owner is 0xCc4c****
Name       | Address    | ETH                   | Token (MST)
---------- | ---------- | --------------------- | ----------
Jason      | 0xCc4c**** | 0.736045099082540281  | 1000000
Orphee     | 0xBf13**** | 0.0                   | 0
---------- | ---------- | --------------------- | ----------
Transferring tokens from Jason to Orphée...
Transferring 16 MST from 0xCc4c**** to 0xBf13****...
    * Creating tx - Used 1.09 seconds
Waiting for tx 0x4273**** to be mined...
    * Waiting tx to be mined - Used 16.63 seconds
Tx 0x4273**** mined successfully.
    From / To     : 0xCc4c**** => 0xf7Df****
    EIP-2718 Type : 2
    Status        : 1
    Block Number  : 7464083
    Block Hash    : 0x7d4c****
    Gas Used      : 56004 (1003264582 wei / gas)
Found Transfer event: 0xCc4c**** => 0xBf13****: 16
Name       | Address    | ETH                   | Token (MST)
---------- | ---------- | --------------------- | ----------
Jason      | 0xCc4c**** | 0.735988912252889953  | 999984
Orphee     | 0xBf13**** | 0.0                   | 16
---------- | ---------- | --------------------- | ----------
Transferring tokens from Orphée to Jason...
Transferring 16 MST from 0xBf13**** to 0xCc4c****...
Error: insufficient funds for intrinsic transaction cost ...
Войдите в полноэкранный режим Выйти из полноэкранного режима

Все работало отлично, за исключением последнего перехода: Он завершился неудачей с ошибкой INSUFFICIENT_FUNDS. На самом деле это имеет смысл, потому что на моем втором тестовом аккаунте (Orphée) нет тестового эфира, чтобы заплатить за газ.

Проверьте контракт

Мы воспользуемся плагином hardhat-etherscan для проверки нашего контракта. Верифицируя контракт, мы загружаем его исходный код, чтобы он соответствовал байткоду контракта, развернутому по заданному адресу. После верификации исходный код смарт-контракта становится общедоступным и проверяемым. Это создает прозрачность и доверие. Это также позволяет Etherscan раскрывать внешние методы нашего контракта через веб-интерфейс.

$ npx hardhat verify 0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783 --network goerli
Nothing to compile
Successfully submitted source code for contract
contracts/SimpleToken.sol:SimpleToken at 0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783
for verification on the block explorer. Waiting for verification result...
Successfully verified contract SimpleToken on Etherscan.
https://goerli.etherscan.io/address/0xf7Df331661C4CFbbBb85Fca9c7b7C3657C50D783#code
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь вернемся в Etherscan и просмотрим наш контракт. На этот раз мы можем видеть все внешние методы нашего контракта и вызывать их через вкладки «Read Contract» и «Write Contract»:

Заключение

Этого достаточно для первого дня. Исходный код первого дня можно найти по адресу: https://github.com/zhengzhong/petshop/releases/tag/day01

Оцените статью
devanswers.ru
Добавить комментарий