使用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项目,您将需要创建一个。为此,您可以执行以下步骤:
-
为您的项目创建一个目录
mkdir hardhat && cd hardhat
-
初始化将创建
package.json
文件的项目npm init -y
-
安装Hardhat
npm install hardhat
-
创建项目
npx hardhat
注意事项
npx
用于运行安装在项目中的本地可执行文件。虽然Hardhat可以全网安装,但是我们建议在每个项目中进行本地安装,这样您可以按项目控制项目版本。 -
系统将会显示菜单,允许您创建新的项目或使用范本项目。在本示例中,您可以选择Create an empty hardhat.config.js
这将在您的项目目录中创建一个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
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添加为网络:
- 导入插件
-
为您的私钥创建变量
请记住
请勿将您的私钥存储至JavaScript文件中。
-
在
module.exports
中,您需要提供Solidity版本 -
添加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
编译后,将会创建一个artifacts
目录:这保存了合约的字节码和元数据,为.json
文件。您可以将此目录添加至您的.gitignore
。
部署合约¶
要部署Box.sol
智能合约,您将需要撰写一个简单的部署脚本。您可以为此脚本创建一个新目录并命名为scripts
,然后为其添加一个名为deploy.js
的新文件:
mkdir scripts && cd scripts
touch deploy.js
接下来,您需要通过ethers
撰写一个部署脚本。因为您将使用Hardhat运行此脚本,所以您无需导入任何代码库。
要开始操作,您可以执行以下步骤:
- 通过
getContractFactory
方法为合约创建一个本地实例 - 使用此实例中存在的
deploy
方法来实例化智能合约 - 使用
deployed
等待部署 - 部署后,您可以使用合约实例获取合约的地址
// 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
中所定义的网络相匹配。
稍等片刻,合约将成功部署,您可以在终端看到合约地址。
恭喜您,您的合约已完成!请保存地址,用于后续与合约实例的交互。
与合约交互¶
要在Moonbase Alpha上与您刚部署的合约交互,您可以通过运行以下命令启动Hardhat console
:
npx hardhat console --network moonbase
接下来,您可以执行以下步骤(一次输入一行):
-
创建一个
Box.sol
合约的本地实例const Box = await ethers.getContractFactory('Box');
-
使用合约地址,将本地实例连接至已部署的合约
const box = await Box.attach('0x425668350bD782D80D457d5F9bc7782A24B8c2ef');
-
与此合约交互。在本示例中,您可以调用
store
方法并存储一个简单的值await box.store(5)
交易将通过您的Moonbase账户进行签署并传送至网络。后台输出将如下所示:
请注意您的地址将被标记为from
,即合约地址,以及正在传送的data
。现在,您可以通过运行以下命令来检索数值:
await box.retrieve();
您将看到5
或者您初始存储的数值。
恭喜您,您已经成功使用Hardhat部署合约并与之交互。
使用Hardhat进行分叉¶
您可以使用Hardhat分叉(fork) 包括Moonbeam在内的任何EVM兼容链。分叉是在本地模拟实时Moonbeam网络,使您可以在本地测试环境中与已部署在Moonbeam上的合约交互。因为Hardhat的分叉是基于EVM实现,您可以通过Moonbeam和Hardhat支持的标准以太坊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/
。在您的终端中,将会显示类似以下输出:
要验证您是否已经分叉好网络,您可以查询最新区块号:
curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
如果您已经将result
从16进制转换成十进制,您应该在分叉网络时获得了最新区块号。您可以使用区块浏览器交叉查询区块号。
在这里,您可以部署新的合约到您的Moonbeam分叉实例,或者通过创建已部署合约的本地实例与已部署合约交互。
要与已部署合约交互,您可以使用ethers
在scripts
目录中创建新的脚本。因为您将使用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;
});
| Created: March 21, 2022