1、运行以太坊本地网络
Hardhat 简介
Hardhat,是一个可以在本地环境中轻松启动一个以太坊网络的工具。它会给我们用于测试的假的以太币和假的的测试账户。它就像服务器,只不过这个“服务器”是区块链。它还可以让我们轻松编写智能合约,并在本地以太坊网络中测试。
安装
我们将首先创建一个项目,并使用 npm 安装 hardhat:
mkdir my-wave-portal
cd my-wave-portal
npm init -y
npm install --save-dev hardhat@latest
创建 hardhat 项目
npx hardhat
如下图,选择选项 Create a JavaScript project
,剩下一路按 enter 键即可:
可以看到提示我们安装hardhat-waffle
和hardhat-ethers
,这些是我们后面会用到的包:
npm install --save-dev hardhat@^2.12.2 @nomicfoundation/hardhat-toolbox@^2.0.0
除此之外,我们还要安装如下我们也需要的包:
npm install --save-dev chai @nomiclabs/hardhat-ethers ethers @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-chai-matchers
最后,运行 npx hardhat node
,这应该会打印出一堆账户,看起来像这样:
这些是 Hardhat为 我们生成的以太坊地址,用于模拟区块链上的真实用户,便于我们测试。
编译
如果一切正常,我们可以运行如下命令,最合约进行编译:
npx hardhat compile
然后运行测试:
npx hardhat test
我们应该能看到如下的样子:
清理
上面测试的其实是项目一开始创建时生成的一些代码,那些代码对我们用处不大,所以应该把它们删除掉:
- 删除 test 目录下的 Lock.js,
- 删除 scripts 目录下的 deploy.js,
- 删除 contracts 目录下的 Lock.sol。
2、编写第一个智能合约
在 contracts
目录下创建一个文件并将其命名为 WavePortal.sol
,文件代码如下:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import "hardhat/console.sol";
contract WavePortal {
constructor() {
console.log("Yoho, I am a contract and I am smart");
}
}
解释:
-
// SPDX-License-Identifier: UNLICENSED
:被称为 “SPDX-License-Identifier”,是必须的,便然你的编辑器会一直有警告的提示。 -
pragma solidity 0.8.17;
:希望合约使用的 Solidity 编译器的版本。基本意思是 “当运行合约的时候,我只想要使用 0.8.17 版本的 Solidity 编译器,低一点都不行。” 注意,要确保编译器的版本在hardhat.config.js
中是一样的。 -
import "hardhat/console.sol";
:在合约中做了一些控制台日志。 -
contract WavePortal
:该智能合约的名字,有点像其他语言中的 "类"。 -
constructor
:构造函数,一旦我们第一次初始化这个合约,这个结构函数就会运行并打印出Yoho, I am a contract and I am smart
。
3、本地编译、运行智能合约
上面我们写了一份合约,现在我们来在本地编译、运行它。
构建运行合约脚本
进入 scripts
目录并创建一个名为 run.js
的文件,要测试智能合约,我们必须正确地做很多事情。比如:编译,部署,然后执行。编写脚本将使我们非常容易地快速迭代我们的合约。
run.js
代码如下:
const main = async () => {
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to: ", waveContract.address);
}
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
}
释义:
-
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
:实际编译我们的合约并在artifacts
目录下生成我们需要使用我们的合约的必要文件。 -
const waveContract = await waveContractFactory.deploy();
:Hardhat 将为我们创建一个本地以太坊网络,但只是为了这个合约。然后,在脚本完成后,它会破坏该本地网络。因此,每次运行合约时,它都会是一个全新的区块链。这有点像每次都刷新你的本地服务器,所以你总是从一个干净的区块链开始,这样可以轻松调试错误。 -
await waveContract.deployed();
:等到我们的合约正式部署到我们的本地区块链,合约的constructor
g构造方法中的代码将被运行(智能合约初始化)。 -
console.log("Contract deployed to:", waveContract.address);
:waveContract.address 是合约的地址。这个合约地址挺重要的,只有通过这个合约地址,我们才能调用合约中的代码。
运行脚本
npx hardhat run scripts/run.js
你应该能看到在本地控制台上,打印出合约的地址来,如下所示:
Hardhat & HRE
上面代码中,你应该注意到使用了 hre.ethers
,而且它不用从任何地方导入 hre
,这是什么情况呢?
直接从 Hardhat 文档,我们会注意到有如下这一点:
Hardhat 运行时环境,或简称 hre,是一个包含 Hardhat 在运行任务、测试或脚本时公开的所有功能的对象。实际上,Hardhat 是 hre。
所以,每次运行以 npx hardhat
开头的终端命令时,都会使用代码中指定的 hardhat.config.js
动态构建这个 hre 对象。这意味着可以不必实际对文件进行某种导入,例如:
const hre = require("hardhat");
所以,我们会在代码中看到很多 hre
,但从未在任何地方导入。可以查看 Hardhat 文档 以了解更多信息。
4、智能合约中数据存储
我们希望能够让某人向我们挥手wave,然后存储该挥手。所以,我们首先需要的是一个他们可以点击向我们挥手的功能。
区块链 = 把它想象成一个云提供商,有点像腾讯云或阿里云,但它不归任何人所有。它由来自世界各地的矿机的计算能力运行。通常这些人被称为矿工,我们付钱给他们来运行我们的代码
智能合约 = 有点像我们服务器的代码,具有人们可以调用的不同功能。
接下来,我们更新合约 WavePortal.sol
的内容如下:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import "hardhat/console.sol";
contract WavePortal {
uint256 totalWaves;
constructor() {
console.log("Yoho, I am a contract and I am smart");
}
function wave() public {
totalWaves += 1;
console.log("%s has waved", msg.sender);
}
function getTotalWaves() public view returns (uint256) {
console.log("We have %d total waves!", totalWaves);
return totalWaves;
}
}
以上就是 Solidity 中编写函数的方式。我们添加了一个初始化为 0 的 totalWaves
变量。这个变量很特别,它被称为“状态变量”,因为它永远存储在合约存储中。
msg.sender
,这是调用该函数的钱包地,它就像内置身份验证。它让我们确切地知道是谁调用了这个函数,所以为了调用智能合约函数,你需要连接一个有效的钱包。
我们可以编写只有特定钱包地址才能命中的函数。例如,我们可以更改此功能,只允许我们定义的白名单地址才可以发送 wave。
更新脚本
基本上,当我们将合约部署到区块链时(运行 waveContractFactory.deploy() 时),我们的函数就可以在区块链上被调用,因为我们在函数上使用了特殊的 public
关键字。可以把它想象成一个公共 API 端点。
所以,更新 run.js
脚本如下:
const main = async () => {
const [owner, randomPerson ] = await hre.ethers.getSigers();
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to: ", waveContract.address);
console.log("Contract deployed by: ", owner.address);
let waveCount;
waveCount = await waveContract.getTotalWaves();
let waveTxn = await waveContract.wave();
await waveTxn.wait();
waveCount = await waveContract.getTotalWaves();
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
释义:
-
const [owner, randomPerson ] = await hre.ethers.getSigers();
:为了将某些东西部署到区块链,我们需要有一个钱包地址。Hardhat 在后台为我们做了这件事,在这里我们抓取了合约所有者的钱包地址(owner),也抓取了一个随机的钱包地址(randomPerson)。 - 为了查看部署合约的地址,添加了一条打印:
console.log("Contract deployed by: ", owner.address);
。 - 接下来,手动调用合约中的函数。先调用获取挥手次数的函数(getTotalWaves),然后调用挥手函数(wave),最后在调用获取挥手次数的函数(getTotalWaves),这样我们就可以观察到
waveCount
是否发生了变化。
运行脚本
npx hardhat run scripts/run.js
输出如下:
可以看到挥手的钱包地址等于部署合约的地址。这是我在对自己挥手。我们
- 调用 wave 函数
- 更改状态变量 totalWaves
- 读取状态变量最新值
这几乎是大多数智能合约的基础,读取函数、编写函数、并改变状态变量。很快,我们将能从vue、react等前端中调用这些函数。
测试其他用户
下面模拟其他用户向我们挥手,run.js
代码更新如下:
const main = async () => {
const [owner, randomPerson ] = await hre.ethers.getSigners();
const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
const waveContract = await waveContractFactory.deploy();
await waveContract.deployed();
console.log("Contract deployed to: ", waveContract.address);
console.log("Contract deployed by: ", owner.address);
let waveCount;
waveCount = await waveContract.getTotalWaves();
let waveTxn = await waveContract.wave();
await waveTxn.wait();
waveCount = await waveContract.getTotalWaves();
// 模拟其他用户连接钱包向我们挥手
waveTxn = waveContract.connect(randomPerson).wave();
await waveTxn.wait();
waveCount = await waveContract.getTotalWaves();
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
运行脚本后之后,如下入所示:
5、编写脚本并在本地部署
启动本地网络
运行 scripts/run.js
不是已经部署到本地网络了吗?
这只是一部分,请记住,当在运行 scripts/run.js
时,它实际上是:
- 创建一个新的本地以太坊网络,
- 部署合约,
- 当脚本运行结束时,Hardhat 将自动销毁该本地网络。
所以,我们需要一种方法来保留本地网络,就像本地服务器,我们要让它保持在运行状态,这个我们才能与它交互。
回到项目根目录,打开新的终端窗口,在窗口中输入以下命令,以启动本地节点:
npx hardhat node
这样我们就启动了一个保持活动的本地以太坊网络,而且,如你所见,Hardhat 为我们提供了 20 个账户,并给了每个账户 10000 ETH。(只是本地的测试币哦)
创建部署脚本
现在,是一个空的区块链,还没有区块。
我们现在要创建一个新区快并在其上获取我们的智能合约。
在 scripts
文件夹下,创建一个名为 deploy.js
的文件。代码如下,看起来与 run.js
非常相似。
const main = async () => {
const [deployer] = await hre.ethers.getSigners();
const accountBalance = await deployer.getBalance();
console.log("Deploying contracts with account: ", deployer.address);
console.log("Account balance: ", accountBalance.toString());
const Token = await hre.ethers.getContractFactory("WavePortal");
const portal = await Token.deploy();
await portal.deployed();
console.log("WavePortal address: ", portal.address);
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.error(error);
process.exit(1);
}
};
runMain();
部署脚本
保持上面启动本地网络的窗口不要关闭,在开启一个新的终端窗口,输入以下运行本地部署脚本命令:
npx hardhat run scripts/deploy.js --network localhost
我们将看到类似的如下输出:
我们部署了合约,我们也在区块链上有了合约的地址。我们的前端网站将需要它,以便知道在区块链上的何处查找合约。 (想象一下,如果必须在整个区块链中搜索我们的合约。那将是多么糟糕)
在本地以太坊网络保持活跃的终端窗口中,我们会看到一些新东西,如下所示: