前言
在之前的《Solidity 智能合约开发 - 基础》中,我们学习了 Solidity 的基本语法,并且了解了可以通过 Brownie 与 HardHat 等框架进行调试。而另一篇《Solidity 智能合约开发 - 玩转 Web3.py》中我们也通过 Web3.py 直接与我们本地的 Ganache 节点进行交互了。
原本因为之前比较熟悉 Python 的使用,所以想使用 Brownie 框架进行后续开发。然而经过了一番调研,业界还是使用 HardHat 框架居多,也有更多拓展,且我关注的 Solidity 教程也更新了 Javascript 版本,于是还是打算学习一下。
为了更好了解其原理,也为我们后续更好使用框架打好基础,我们这次通过 ethers.js 来与我们部署在 Alchemy 平台上的 Rinkeby 测试网络进行交互。实现了基础的合约编译、部署至 Rinkeby 网络、与合约交互等功能。
可以点击这里访问本测试 Demo 代码仓库。
ethers.js
ethers.js 是 Javascript 的一个开源库,可以与以太坊网络进行交互,其 GitHub 地址为 ethers.io/ethers.js,可以访问其官方文档进行使用。
安装
我们可以通过 yarn 安装 ethers.js
,如下:
yarn add ethers
使用
使用 require
导入库即可使用
const ethers = require('ethers');
Solidity 合约编译
合约源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract SimpleStorage {
uint256 favoriteNumber;
bool favoriteBool;
struct People {
uint256 favoriteNumber;
string name;
}
People public person = People({favoriteNumber: 2, name: "Arthur"});
People[] public people;
mapping(string => uint256) public nameToFavoriteNumber;
function store(uint256 _favoriteNumber) public returns (uint256) {
favoriteNumber = _favoriteNumber;
return favoriteNumber;
}
function retrieve() public view returns (uint256) {
return favoriteNumber;
}
function addPerson(string memory _name, uint256 _favoriteNumber) public {
people.push(People({favoriteNumber: _favoriteNumber, name: _name}));
nameToFavoriteNumber[_name] = _favoriteNumber;
}
}
这是一个简单的存储合约,通过一个 People 结构体对象来存储人名和他喜欢数字,通过一个数组来存储多个人的信息,并提供了添加、查找方法。
读取合约源文件
当我们通过 VSCode 或其他编辑器完成 Solidity 合约编写与语法检查后,需要编译合约为 abi 文件与 bytecode。
我们可以通过 yarn
安装 solc
命令行工具进行编辑,并且可以选择对应版本,命令如下:
yarn add [email protected]
安装完成后,,我们可以通过 solcjs
命令来进行编译,命令如下:
yarn solcjs --bin --abi --include-path node_modules/ --base-path . -o . SimpleStorage.sol
因为编译合约是一个高频操作,我们可以在 package.json
中配置 compile
脚本命令,如下:
"scripts": {
"compile": "yarn solcjs --bin --abi --include-path node_modules/ --base-path . -o . SimpleStorage.sol"
}
之后仅需执行 yarn compile
即可生成合约编译文件。
获取编译结果
编译完成后会生成 abi 和 bytecode 文件,分别以 .bin
和 .abi
为后缀。
获取 bytecode 与 abi
Solidity 合约的部署与交互需要 bytecode 与 abi 两个部分,我们可以通过通过以下代码将其写入对应变量供后续操作使用。
const fs = require('fs-extra');
const abi = fs.readFileSync("./SimpleStorage_sol_SimpleStorage.abi", "utf-8");
const binary = fs.readFileSync("./SimpleStorage_sol_SimpleStorage.bin", "utf-8");
创建 Rinkeby 测试网络环境(Alchemy)
智能合约的调试需要将合约部署到实际的链上,我们选择部署到 Alchemy 平台的 Rinkeby 测试网进行后续调试开发,
Alchemy 平台
首先我们访问 Alchemy 官网,注册并登录,会看到其 Dashboard,会展示所有已创建的应用。
安装完成后选择 Create App 即可快速创建一个 Rinkeby 测试网络节点。
创建完成后,点击 View Details,可以看到我们刚创建的 App 详细信息,点击右上角 View Key,可以查询我们的节点信息,我们需要记录下 HTTP URL,供后续连接使用。
创建 Rinkeby 测试账户(MetaMask)
MetaMask
完成了 Rinkeby 测试网络环境的创建,我们需要通过 MetaMask 创建账户,获取一些测试 Token,并且将账户私钥记录下来,以便后续使用。
获取测试 Token
创建账户后,我们需要一些测试 Token 来进行后续开发调试,我们可以通过以下网址获取:
连接测试节点与钱包
连接节点
ethers.js
提供了库可以方便地连接到我们的测试节点,其中 process.env.ALCHEMY_RPC_URL
为我们在 Alchemy 平台创建 App 的 HTTP URL:
const ethers = require('ethers');
const provider = new ethers.providers.JsonRpcProvider(process.env.ALCHEMY_RPC_URL);
连接钱包
ethers.js
也提供了方法可以连接到我们的测试钱包,其中 process.env.RINKEBY_PRIVATE_KEY
为我们从 MetaMask 复制的私钥。
const ethers = require('ethers');
const wallet = new ethers.Wallet(
process.env.RINKEBY_PRIVATE_KEY,
provider
);
Solidity 合约部署
创建合约
我们可以通过 ethers.js
库创建合约。
const contractFactory = new ethers.ContractFactory(abi, binary, wallet);
部署合约
下面我们介绍一下如何通过 ethers.js
库部署合约,其中 SimpleStorage
合约的 ABI 和 BIN 文件已经在上面的代码中读取过了。
创建合约
const contractFactory = new ethers.ContractFactory(abi, binary, wallet);
部署合约
const contract = await contractFactory.deploy();
await contract.deployTransaction.wait(1);
与合约交互
我们也可以通过 ethers.js
来与合约进行交互。
retrieve()
const currentFavoriteNumber = await contract.retrieve();
store()
const transactionResponse = await contract.store("7")
const transactionReceipt = await transactionResponse.wait(1);
从 raw data 构造交易
除了直接调用部署合约方法等,我们也可以自己构造交易。
构造交易
const nonce = await wallet.getTransactionCount();
const tx = {
nonce: nonce,
gasPrice: 20000000000,
gasLimit: 1000000,
to: null,
value: 0,
data: "0x" + binary,
chainId: 1337,
};
签名交易
const signedTx = await wallet.signTransaction(tx);
发送交易
const sentTxResponse = await wallet.sendTransaction(tx);
await sentTxResponse.wait(1);
总结
以上就是我们通过 ethers.js
库与 Alchemy 的 Rinkeby 测试网络进行交互的步骤,在真正的生产项目开发中我们一般不会直接使用 ethers.js
这样的库,而是会使用 Brownie、HardHat 这样进一步封装的框架,但了解 Web3.py
或 ethers.js
等库的使用方法也非常重要。后续我还会对 HardHat 框架的使用作进一步讲解。