部署和交互智能合约
设置本地区块链
在我们开始之前,我们首先需要一个可以部署合约的环境。Ethereum 区块链(通常称为“mainnet”,即“主网络”)需要花费真金白银才能使用,以 Ether(其原生货币)的形式。这使得它在尝试新想法或工具时成为一个糟糕的选择。
为了解决这个问题,存在许多“testnets”(即“测试网络”):这些包括 Sepolia 和 Holesky 区块链。它们的工作方式与 mainnet 非常相似,但有一个区别:你可以免费获得这些网络的 Ether,因此使用它们不需要花费一分钱。但是,你仍然需要处理私钥管理、12 秒或更长的区块时间,以及实际获得这些免费的 Ether。
在开发过程中,最好使用_本地_区块链。它在你的机器上运行,不需要访问互联网,为你提供所有你需要的 Ether,并立即挖掘区块。这些原因也使本地区块链非常适合 自动化测试。
如果你想学习如何在_公共_区块链(如 Ethereum 测试网络)上部署和使用合约,请前往我们的 连接到公共测试网络指南。 |
Hardhat 自带一个内置的本地区块链,即 Hardhat 网络。
启动后,Hardhat 网络将创建一组未锁定的帐户并为其提供 Ether。
$ npx hardhat node
Hardhat 网络将打印出其地址 http://127.0.0.1:8545
,以及可用帐户及其私钥的列表。
请记住,每次运行 Hardhat 网络时,它都会创建一个全新的本地区块链 - 不会保留先前运行的状态。这对于短期实验来说是可以的,但这意味着你需要在这些指南的整个过程中打开一个窗口运行 Hardhat 网络。
当未指定网络且未配置默认网络或默认网络设置为 hardhat 时,Hardhat 将始终启动一个 Hardhat 网络实例。
|
你也可以在_https://geth.ethereum.org/getting-started/dev-mode[开发模式]_下运行一个真正的 Ethereum 节点。这些设置起来有点复杂,并且在测试和开发方面不够灵活,但更能代表真实的网络。 |
部署智能合约
在 开发智能合约指南中,我们设置了我们的开发环境。
我们将创建一个脚本来部署我们的 Box 合约。我们将此文件另存为 scripts/deploy.js
。
// scripts/deploy.js
async function main () {
// We get the contract to deploy
// 我们得到了要部署的合约
const Box = await ethers.getContractFactory('Box');
console.log('Deploying Box...');
// 部署 Box 中...
const box = await Box.deploy();
await box.waitForDeployment();
console.log('Box deployed to:', await box.getAddress());
// Box 部署到:
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
我们在脚本中使用 ethers,因此我们需要安装它和 @nomicfoundation/hardhat-ethers 插件。
$ npm install --save-dev @nomicfoundation/hardhat-ethers ethers
我们需要在我们的 配置中添加我们正在使用的 @nomicfoundation/hardhat-ethers
插件。
// hardhat.config.js
require("@nomicfoundation/hardhat-ethers");
...
module.exports = {
...
};
使用 run
命令,我们可以将 Box
合约部署到本地网络 (Hardhat Network):
$ npx hardhat run --network localhost scripts/deploy.js
Deploying Box...
// 部署 Box 中...
Box deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
// Box 部署到:0x5FbDB2315678afecb367f032d93F642f64180aa3
Hardhat 不会跟踪你部署的合约。我们在脚本中显示了部署的地址(在我们的示例中,0x5FbDB2315678afecb367f032d93F642f64180aa3 )。这在以编程方式与它们交互时会很有用。
|
全部完成!在实际的网络上,此过程需要花费几秒钟的时间,但在本地区块链上几乎是即时的。
如果你遇到连接错误,请确保你在另一个终端中运行 本地区块链。 |
请记住,本地区块链不会在多次运行中保留其状态!如果你关闭本地区块链进程,则必须重新部署合约。 |
从控制台交互
有了我们 部署的 Box
合约,我们可以立即开始使用它。
我们将使用 Hardhat 控制台与我们在 localhost 网络上部署的 Box
合约进行交互。
我们需要指定在部署脚本中显示的 Box 合约的地址。
|
重要的是,我们显式地设置 Hardhat 连接我们的控制台会话的网络。如果我们不这样做,Hardhat 将默认使用一个新的临时网络,我们的 Box 合约将不会部署到该网络。 |
$ npx hardhat console --network localhost
Welcome to Node.js v20.17.0.
Type ".help" for more information.
> const Box = await ethers.getContractFactory('Box');
undefined
> const box = Box.attach('0x5FbDB2315678afecb367f032d93F642f64180aa3')
undefined
发送交易
Box
的第一个函数 store
接收一个整数值并将其存储在合约存储中。由于此函数_修改_了区块链状态,我们需要_发送交易_到合约以执行它。
我们将发送一个交易来调用带有数值的 store
函数:
> await box.store(42)
{
hash: '0x3d86c5c2c8a9f31bedb5859efa22d2d39a5ea049255628727207bc2856cce0d3',
...
查询状态
Box
的另一个函数称为 retrieve
,它返回存储在合约中的整数值。这是区块链状态的_查询_,因此我们不需要发送交易:
> await box.retrieve()
42n
由于查询仅读取状态并且不发送交易,因此没有要报告的交易哈希。这也意味着使用查询不会花费任何 Ether,并且可以在任何网络上免费使用。
我们的 Box 合约返回 uint256 ,对于 JavaScript 来说这个数字太大了,所以我们返回了一个大数字对象。我们可以使用 (await box.retrieve()).toString() 将大数字显示为字符串。
|
> (await box.retrieve()).toString()
'42'
要了解更多关于使用控制台的信息,请查看 Hardhat 文档。 |
以编程方式交互
控制台对于原型设计和运行一次性查询或交易很有用。但是,最终你将希望从你自己的代码中与你的合约进行交互。
在本节中,我们将学习如何从 JavaScript 与我们的合约进行交互,并使用 Hardhat 运行带有 Hardhat 配置的脚本。
请记住,还有许多其他 JavaScript 库可用,你可以使用你最喜欢的任何一个。一旦部署了合约,你就可以通过任何库与它进行交互! |
设置
让我们开始在一个新的 scripts/index.js
文件中编写代码,我们将在其中编写我们的 JavaScript 代码,从一些样板代码开始,包括用于 编写异步代码。
// scripts/index.js
async function main () {
// Our code will go here
// 我们的代码将放在这里
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
我们可以通过向本地节点询问一些信息来测试我们的设置,例如已启用的帐户列表:
// Retrieve accounts from the local node
// 从本地节点检索帐户
const accounts = (await ethers.getSigners()).map(signer => signer.address);
console.log(accounts);
我们不会在每个代码片段上重复样板代码,但请确保始终在我们上面定义的 main 函数_内部_编写代码!
|
使用 hardhat run
运行上面的代码,并检查你是否获得了响应中的可用帐户列表。
$ npx hardhat run --network localhost ./scripts/index.js
[
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
...
]
这些帐户应与你之前启动 本地区块链 时显示的帐户匹配。现在我们有了第一个从区块链中获取数据的代码片段,让我们开始使用我们的合约。记住,我们正在我们上面定义的 main
函数_内部_添加我们的代码。
获取合约实例
为了与我们部署的 Box
合约进行交互,我们将使用 ethers 合约实例。
ethers 合约实例是一个 JavaScript 对象,它表示我们在区块链上的合约,我们可以使用它与我们的合约进行交互。要将其附加到我们部署的合约,我们需要提供合约地址。
// Set up an ethers contract, representing our deployed Box instance
// 设置一个 ethers 合约,表示我们部署的 Box 实例
const address = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const Box = await ethers.getContractFactory('Box');
const box = Box.attach(address);
确保将 address 替换为你部署合约时获得的地址,该地址可能与此处显示的地址不同。
|
我们现在可以使用这个 JavaScript 对象与我们的合约进行交互。
调用合约
让我们从显示 Box
合约的当前值开始。
我们需要调用合约的只读 retrieve()
公共方法,并 等待响应:
// Call the retrieve() function of the deployed Box contract
// 调用已部署的 Box 合约的 retrieve() 函数
const value = await box.retrieve();
console.log('Box value is', value.toString());
// Box 值为
此代码片段等效于我们之前从控制台运行的 查询。现在,通过再次运行该脚本并检查打印的值来确保一切运行顺利:
$ npx hardhat run --network localhost ./scripts/index.js
Box value is 42
// Box 值为 42
发送交易
我们现在将发送一个交易来 store
一个新值到我们的 Box 中。
让我们在我们的 Box
中存储一个 23
的值,然后使用我们之前编写的代码来显示更新后的值:
// Send a transaction to store() a new value in the Box
// 发送一个交易以在 Box 中 store() 一个新值
await box.store(23);
// Call the retrieve() function of the deployed Box contract
// 调用已部署的 Box 合约的 retrieve() 函数
const value = await box.retrieve();
console.log('Box value is', value.toString());
// Box 值为
在实际应用中,你可能需要 估算你的交易的 Gas,并检查 Gas 价格预言机以了解在每个交易中使用的最佳值。 |
我们现在可以运行该代码片段,并检查 box 的值是否已更新!
$ npx hardhat run --network localhost ./scripts/index.js
Box value is 23
// Box 值为 23