【构建以太坊Dapp】-1-创建第一个智能合约

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 项目

可以看到提示我们安装hardhat-wafflehardhat-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

我们应该能看到如下的样子:

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

输出如下:

运行脚本

可以看到挥手的钱包地址等于部署合约的地址。这是我在对自己挥手。我们

  1. 调用 wave 函数
  2. 更改状态变量 totalWaves
  3. 读取状态变量最新值

这几乎是大多数智能合约的基础,读取函数、编写函数、并改变状态变量。很快,我们将能从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 时,它实际上是:

  1. 创建一个新的本地以太坊网络,
  2. 部署合约,
  3. 当脚本运行结束时,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

我们将看到类似的如下输出:

部署脚本

我们部署了合约,我们也在区块链上有了合约的地址。我们的前端网站将需要它,以便知道在区块链上的何处查找合约。 (想象一下,如果必须在整个区块链中搜索我们的合约。那将是多么糟糕)

在本地以太坊网络保持活跃的终端窗口中,我们会看到一些新东西,如下所示:

部署脚本

你可能感兴趣的:(【构建以太坊Dapp】-1-创建第一个智能合约)