Skip to content

使用Hardhat部署至Moonbeam

概览

Hardhat是一个以太坊开发环境,可帮助开发人员管理和自动化构建智能合约和DApp所固有的重复性任务。Hardhat可以直接与Moonbeam的以太坊API交互,因此可以在部署智能合约至Moonbeam时使用。

本教程将涵盖如何使用Hardhat在Moonbase Alpha测试网上编译、部署和调试以太坊智能合约。本教程也同样适用于Moonbeam、Moonriver和Moonbeam开发节点。

查看先决条件

在开始之前,您将需要准备以下内容:

  • 安装MetaMask并将其连接至Moonbase Alpha
  • 具有拥有一定数量资金的账户。 您可以每24小时一次从Moonbase Alpha水龙头上获取DEV代币以在Moonbase Alpha上进行测试
  • 要在Moonbeam或Moonriver网络上测试本指南中的示例,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥

创建Hardhat项目

如果您还未有Hardhat项目,您将需要创建一个。为此,您可以执行以下步骤:

  1. 为您的项目创建一个目录

    mkdir hardhat && cd hardhat
    
  2. 初始化将创建package.json文件的项目

    npm init -y
    
  3. 安装Hardhat

    npm install hardhat
    
  4. 创建项目

    npx hardhat init
    

    注意事项

    npx用于运行安装在项目中的本地可执行文件。虽然Hardhat可以全网安装,但是我们建议在每个项目中进行本地安装,这样您可以按项目控制项目版本。

  5. 系统将会显示菜单,允许您创建新的项目或使用范本项目。在本示例中,您可以选择Create an empty hardhat.config.js

Hardhat Create Project

这将在您的项目目录中创建一个Hardhat配置文件(hardhat.config.js)。

Hardhat项目创建完毕后,您可以安装Ethers plugin。这将为使用Ethers.js代码库与网络交互提供一种简便方式。您可以运行以下命令进行安装:

npm install @nomicfoundation/hardhat-ethers ethers

合约文件

现在您已经创建了一个空白的项目,接下来您可以通过运行以下命令创建一个contracts目录:

mkdir contracts && cd contracts

将要作为示例部署的智能合约被称为Box,这将存储一个数值,用于稍后检索使用。在contracts目录中,您可以创建Box.sol文件:

touch Box.sol

打开文件,并为其添加以下合约内容:

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;

contract Box {
    uint256 private value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);

    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

Hardhat配置文件

在部署合约至Moonbase Alpha之前,您将需要修改Hardhat配置文件,并创建一个安全的文件以便您存储私钥。

接下来,您可以遵循以下步骤修改hardhat.config.js文件并将Moonbase Alpha添加为网络:

  1. 导入插件
  2. 为您的私钥创建变量

    请记住

    请勿将您的私钥存储至JavaScript文件中。

  3. module.exports中,您需要提供Solidity版本

  4. 添加Moonbase Alpha网络配置。 您可以修改hardhat.config.js文件,使其可用于任何Moonbeam网络:

    moonbeam: {
        url: 'INSERT_RPC_API_ENDPOINT', // Insert your RPC URL here
    
        chainId: 1284, // (hex: 0x504)
        accounts: [privateKey]
      },
    
    moonriver: {
        url: 'INSERT_RPC_API_ENDPOINT', // Insert your RPC URL here
    
        chainId: 1285, // (hex: 0x505)
        accounts: [privateKey]
      },
    
    moonbase: {
        url: 'https://rpc.api.moonbase.moonbeam.network',
    
        chainId: 1287, // (hex: 0x507)
        accounts: [privateKey]
      },
    
    dev: {
        url: 'http://127.0.0.1:9944',
    
        chainId: 1281, // (hex: 0x501)
        accounts: [privateKey]
      },
    
// 1. Import the Ethers plugin required to interact with the contract
require('@nomicfoundation/hardhat-ethers');

// 2. Add your private key from your pre-funded Moonbase Alpha testing account
const privateKey = 'INSERT_PRIVATE_KEY';

module.exports = {
  // 3. Specify the Solidity version
  solidity: "0.8.1",

  networks: {
    // 4. Add the Moonbase Alpha network specification
    moonbase: {
      url: 'https://rpc.api.moonbase.moonbeam.network',
      chainId: 1287, // 0x507 in hex,
      accounts: [privateKey]
    }
  }
};

恭喜您!您现在可以开始部署了!

编译Solidity

您可以简单运行以下命令编译合约:

npx hardhat compile

Hardhat Contract Compile

编译后,将会创建一个artifacts目录:这保存了合约的字节码和元数据,为.json文件。您可以将此目录添加至您的.gitignore

部署合约

要部署Box.sol智能合约,您将需要撰写一个简单的部署脚本。您可以为此脚本创建一个新目录并命名为scripts,然后为其添加一个名为deploy.js的新文件:

mkdir scripts && cd scripts
touch deploy.js

接下来,您需要通过ethers撰写一个部署脚本。因为您将使用Hardhat运行此脚本,所以您无需导入任何代码库。

要开始操作,您可以执行以下步骤:

  1. 通过getContractFactory方法为合约创建一个本地实例
  2. 使用此实例中存在的deploy方法来实例化智能合约
  3. 使用waitForDeployment等待部署
  4. 部署后,您可以使用合约实例获取合约的地址
// scripts/deploy.js
async function main() {
  // 1. Get the contract to deploy
  const Box = await ethers.getContractFactory('Box');
  console.log('Deploying Box...');

  // 2. Instantiating a new Box smart contract
  const box = await Box.deploy();

  // 3. Waiting for the deployment to resolve
  await box.waitForDeployment();

  // 4. Use the contract instance to get the contract address
  console.log('Box deployed to:', box.target);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

您现在可以使用run命令并指定moonbase作为网络来部署Box.sol合约:

npx hardhat run --network moonbase scripts/deploy.js

如果您正在使用另一个Moonbeam网络,请确保您已指定正确的网络。网络名称需要与hardhat.config.js中所定义的网络相匹配。

稍等片刻,合约将成功部署,您可以在终端看到合约地址。

Hardhat Contract Deploy

恭喜您,您的合约已完成!请保存地址,用于后续与合约实例的交互。

与合约交互

要在Moonbase Alpha上与您刚部署的合约交互,您可以通过运行以下命令启动Hardhat console

npx hardhat console --network moonbase

接下来,您可以执行以下步骤(一次输入一行):

  1. 创建一个Box.sol合约的本地实例

    const Box = await ethers.getContractFactory('Box');
    
  2. 使用合约地址,将本地实例连接至已部署的合约

    const box = await Box.attach('0x425668350bD782D80D457d5F9bc7782A24B8c2ef');
    
  3. 与此合约交互。在本示例中,您可以调用store方法并存储一个简单的值

    await box.store(5);
    

交易将通过您的Moonbase账户进行签署并传送至网络。后台输出将如下所示:

Transaction output

请注意您的地址将被标记为from,即合约地址,以及正在传送的data。现在,您可以通过运行以下命令来检索数值:

await box.retrieve();

您将看到5或者您初始存储的数值。

恭喜您,您已经成功使用Hardhat部署合约并与之交互。

使用Hardhat进行分叉

您可以使用Hardhat分叉(fork) 包括Moonbeam在内的任何EVM兼容链。分叉是在本地模拟实时Moonbeam网络,使您可以在本地测试环境中与已部署在Moonbeam上的合约交互。因为Hardhat的分叉是基于EVM实现,您可以通过MoonbeamHardhat支持的标准以太坊JSON-RPC方法与分叉网络交互。

您需要了解一些使用Hardhat进行分叉的注意事项。您无法与任何Moonbeam预编译合约及其函数交互。预编译是Substrate实现的一部分,因此无法在模拟的EVM环境中复制。这将阻止您与Moonbeam上的跨链资产和基于Substrate的功能(例如质押和治理)进行交互。

当前分叉Moonbeam存在一个问题,为了解决此问题,您需要先手动修补Hardhat。您可以通过GitHub上的问题和相关PR获取更多信息。

修补Hardhat

在开始之前,您需要应用一个临时补丁来解决RPC错误,直到Hardhat修复了根本问题。错误如下所示:

Error HH604: Error running JSON-RPC server: Invalid JSON-RPC response's result.

Errors: Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/0: RpcTransaction/accessList: Array<{ address: DATA, storageKeys: Array<DATA> | null }> | undefined, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/1: RpcTransaction/accessList: Array<{ address: DATA, storageKeys: Array<DATA> | null }> | undefined, Invalid value null supplied to : RpcBlockWithTransactions | null/transactions: RpcTransaction Array/2: RpcTransaction/accessList: Array<{ address: DATA, storageKeys: Array<DATA> | null }> | undefined

要修补Hardhat,您需要打开您项目中的node_modules/hardhat/internal/hardhat-network/jsonrpc/client.js文件。接下来,添加addAccessList函数并更新_perform_performBatch函数。

现在,您可以移除预先存在的_perform_performBatch函数,并在其中添加以下代码片段:

  addAccessList(method, rawResult) {
    if (
      method.startsWith('eth_getBlock') &&
      rawResult &&
      rawResult.transactions?.length
    ) {
      rawResult.transactions.forEach((t) => {
        if (t.accessList == null) t.accessList = [];
      });
    }
  }
  async _perform(method, params, tType, getMaxAffectedBlockNumber) {
    const cacheKey = this._getCacheKey(method, params);
    const cachedResult = this._getFromCache(cacheKey);
    if (cachedResult !== undefined) {
      return cachedResult;
    }
    if (this._forkCachePath !== undefined) {
      const diskCachedResult = await this._getFromDiskCache(
        this._forkCachePath,
        cacheKey,
        tType
      );
      if (diskCachedResult !== undefined) {
        this._storeInCache(cacheKey, diskCachedResult);
        return diskCachedResult;
      }
    }
    const rawResult = await this._send(method, params);
    this.addAccessList(method, rawResult);
    const decodedResult = (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(
      rawResult,
      tType
    );
    const blockNumber = getMaxAffectedBlockNumber(decodedResult);
    if (this._canBeCached(blockNumber)) {
      this._storeInCache(cacheKey, decodedResult);
      if (this._forkCachePath !== undefined) {
        await this._storeInDiskCache(this._forkCachePath, cacheKey, rawResult);
      }
    }
    return decodedResult;
  }
  async _performBatch(batch, getMaxAffectedBlockNumber) {
    // Perform Batch caches the entire batch at once.
    // It could implement something more clever, like caching per request
    // but it's only used in one place, and those other requests aren't
    // used anywhere else.
    const cacheKey = this._getBatchCacheKey(batch);
    const cachedResult = this._getFromCache(cacheKey);
    if (cachedResult !== undefined) {
      return cachedResult;
    }
    if (this._forkCachePath !== undefined) {
      const diskCachedResult = await this._getBatchFromDiskCache(
        this._forkCachePath,
        cacheKey,
        batch.map((b) => b.tType)
      );
      if (diskCachedResult !== undefined) {
        this._storeInCache(cacheKey, diskCachedResult);
        return diskCachedResult;
      }
    }
    const rawResults = await this._sendBatch(batch);
    const decodedResults = rawResults.map((result, i) => {
      this.addAccessList(batch[i].method, result);
      return (0, decodeJsonRpcResponse_1.decodeJsonRpcResponse)(
        result,
        batch[i].tType
      );
    });
    const blockNumber = getMaxAffectedBlockNumber(decodedResults);
    if (this._canBeCached(blockNumber)) {
      this._storeInCache(cacheKey, decodedResults);
      if (this._forkCachePath !== undefined) {
        await this._storeInDiskCache(this._forkCachePath, cacheKey, rawResults);
      }
    }
    return decodedResults;
  }

然后,您可以通过运行以下命令使用patch-package自动应用代码包:

npx patch-package hardhat

随即会创建一个patches目录,现在您可以分叉Moonbeam且在运行时不会遇到任何错误了。

分叉Moonbeam

您可以从命令行分叉Moonbeam或配置您的Hardhat项目以始终从您的hardhat.config.js文件运行此分叉。要分叉Moonbeam或Moonriver,需要用到您自己的端点和API密钥,您可以从端点提供商所支持的列表中获取。

要从命令行分叉Moonbeam,您可以从您的Hardhat项目目录中运行以下命令:

npx hardhat node --fork INSERT_RPC_API_ENDPOINT
npx hardhat node --fork INSERT_RPC_API_ENDPOINT
npx hardhat node --fork https://rpc.api.moonbase.moonbeam.network

如果您想要配置您的Hardhat项目,您可以使用以下配置更新您的hardhat.config.js文件:

...
networks: {
  hardhat: {
    forking: {
      url: 'INSERT_RPC_API_ENDPOINT',
    },
  },
},
...
...
networks: {
  hardhat: {
    forking: {
      url: 'INSERT_RPC_API_ENDPOINT',
    },
  },
},
...
...
networks: {
  hardhat: {
    forking: {
      url: 'https://rpc.api.moonbase.moonbeam.network',
    },
  },
},
...

当您启动Hardhat分叉时,您会有20个预先注资10,000个测试Token的开发账户。分叉好的实例位于http://127.0.0.1:8545/。在您的终端中,将会显示类似以下输出:

Forking terminal screen

要验证您是否已经分叉好网络,您可以查询最新区块号:

curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545 

如果您已经将result从16进制转换成十进制,您应该在分叉网络时获得了最新区块号。您可以使用区块浏览器交叉查询区块号。

在这里,您可以部署新的合约到您的Moonbeam分叉实例,或者通过创建已部署合约的本地实例与已部署合约交互。

要与已部署合约交互,您可以使用ethersscripts目录中创建新的脚本。因为您将使用Hardhat运行此脚本,因此您无需导入任何库。在脚本中,您可以使用以下代码片段访问网络上的实时合约。

const hre = require('hardhat');

async function main() {
  const provider = new ethers.providers.StaticJsonRpcProvider(
    'http://127.0.0.1:8545/'
  );
  const contract = new ethers.Contract(
    'INSERT_CONTRACT_ADDRESS',
    'INSERT_CONTRACT_ABI',
    provider
  );
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
本网站的所有信息由第三方提供,仅供参考之用。Moonbeam文档网站(https://docs.moonbeam.network/)上列出和描述的任何项目与Moonbeam立场无关。Moonbeam Foundation不保证网站信息的准确性、完整性或真实性。如使用或依赖本网站信息,需自行承担相关风险,Moonbeam Foundation不承担任何责任和义务。这些材料的所有陈述和/或意见由提供方个人或实体负责,与Moonbeam Foundation立场无关,概不构成任何投资建议。对于任何特定事项或情况,应寻求专业权威人士的建议。此处的信息可能会包含或链接至第三方提供的信息与/或第三方服务(包括任何第三方网站等)。这类链接网站不受Moonbeam Foundation控制。Moonbeam Foundation对此类链接网站的内容(包括此类链接网站上包含的任何信息或资料)概不负责也不认可。这些链接内容仅为方便访客而提供,Moonbeam Foundation对因您使用此信息或任何第三方网站或服务提供的信息而产生的所有责任概不负责。
Last update: January 25, 2024
| Created: March 21, 2022