如何建立一个Solidity智能合约项目? - Oliver


本文是有关使用 VSCode、Hardhat、Waffle 和 ethers.js 创建 Solidity 项目教程。
有很多编程语言。但是仅仅为了创建智能合约而创建的并不多。那些在区块链上运行的程序。创建加密货币、NFT 或整个去中心化交易所 (DEX) 的程序。
如果您想使用 Solidity 进行智能合约开发,那么本文适合您。好吧,即使您以前从未听说过 Solidity 和智能合约,遵循本文也可能会激发您进行更深入的挖掘,甚至可能创建您自己的加密货币或 DEX。
本文是一份指南,旨在帮助您建立您的第一个 Solidity 项目。您将学习如何为 Solidity 设置 Visual Studio Code、安装 Hardhat 作为本地开发环境、编写 Solidity 的第一行、测试您的合约,然后将其部署到本地测试网。
 
设置
让我们从设置开始,让您准备好开始创建您的第一个项目!
先决条件
本文假设您已经安装了以下工具:


为 Solidity 设置 Visual Studio Code
与任何其他编辑器相比,Visual Studio Code 有两个巨大的优势:
  1. 它非常受欢迎,背后有一个庞大的社区。
  2. 几乎所有东西都有一个插件。Solidity 在这里没有什么特别之处,因此您只需单击一下即可找到一个运行良好的 Solidity 插件。

快速跳转到Visual Studio Marketplace并安装 Solidity 插件。
你得到的是编译器管理、语法高亮、片段支持、快速编译、代码完成等等。它目前是使用 Visual Studio 代码进行 Solidity 开发的第一大插件,因此您在这里做出了可靠的选择。
完成后,您可以开始创建项目。
 
创建项目
现在您已准备好初始化项目并安装必要的依赖项。
初始化 NPM 项目
最突出的 Solidity 工具是基于 JavaScript 的。它在 Node 上运行并且需要一个 npm 项目。它基本上就像您过去可能做过的任何其他 Web 开发项目一样。
因此,您必须做的第一件事是为您的新项目创建一个文件夹。所以,继续创建一个新文件夹。我会打电话给我的solidity-project。
当你有那个文件夹时,跳转到它并初始化一个新的 npm 项目:
npm init -y

这会为您创建一个基本的 package.json,如下所示。

{
  "name": "solidity-project",
 
"version": "1.0.0",
 
"description": "",
 
"main": "index.js",
 
"scripts": {
   
"test": "echo \"Error: no test specified\" && exit 1"
  },
 
"keywords": [],
 
"author": "Oliver Jumpertz",
 
"license": "ISC"
}


 
安装Hardhat
Hardhat 是本地的 Solidity 开发环境。它以 npm 包的形式提供,并带有许多工具,使您能够轻松地使用 Solidity 进行开发。它有一个任务运行器,为你管理 Solidity 编译器版本,并带有一个测试环境,允许你在本地测试你的合约。
Hardhat 有一个初始化命令,你可以用 npx 执行。所以继续执行以下命令:
npx hardhat

您现在可以选择创建一个示例项目,但现在使用空的Hardhat 配置,因为您将从头开始构建您的项目。
执行该命令时,您的项目中应该有一个名为hardhat.config.js. 您可能已经知道此类文件。几乎所有的 JavaScript 工具都有配置文件,Hardhat 也不例外。
它应该是这样的:

/**
 * @type import('hardhat/config').HardhatUserConfig
 */

module.exports = {
  solidity:
"0.7.3",
};

0.7.3遗憾的是有点过时了,所以将其更新为0.8.5(这是撰写本文时 Solidity 的最新版本)
 
在本地安装Hardhat
Hardhat 的 init 命令只为你创建了一个配置文件,但它并没有改变你的package.json. 这就是您现在需要手动安装安全帽作为开发依赖项的原因。
执行以下命令:
npm install --save-dev hardhat

现在你已经在你的项目中安装了安全帽。您可以随时通过 npx 执行 Hardhat,但拥有项目本地版本通常是个好主意。这可以防止因更新和新版本中的意外更改而发生冲突。
 
创建文件夹结构
接下来,您需要创建 Solidity 项目的三个基本文件夹:

  1. contracts 是您的 Solidity 文件所在的位置。
  2. scripts 是你存储安全帽脚本的地方(记得我说过它带有任务运行器吗?脚本基本上是安全帽任务)。
  3. test 是您的合同测试登陆的地方。

创建它们后,您就可以安装更多依赖项了。
 
安装Hardhat插件
Hardhat主要通过插件工作。包本身提供所有基本功能,而插件处理更具体的任务,如测试。
现在是时候安装更多依赖项了,因此执行以下命令来拉入依赖项:
npm install --save-dev @nomiclabs/hardhat-waffle @nomiclabs/hardhat-ethers ethereum-waffle chai  ethers solidity-coverage

这些是更多的依赖项,所以让我们来看看它们现在是什么或做什么:

  1. @nomiclabs /hardhat-waffle:这是一个启用waffle 支持的Hardhat插件。
  2. @nomiclabs /hardhat-ethers:这是一个支持 ethers 的 Hardhat 插件。
  3. ethereum-waffle:Waffle 是一个 Solidity 测试库。它允许您使用 JavaScript 为您的合约编写测试。
  4. chai : Chai 是一个断言库,提供诸如expect.
  5. ethers:这是一个流行的以太坊客户端库。它允许您与实现以太坊 API 的区块链交互。
  6. solidity-coverage:这个库在Istanbul的帮助下为您提供单元测试的覆盖率报告。

 
激活插件
要使插件变得活跃,您需要告诉 Hardhat 积极使用它们。不过,这项任务相对简单。您只需要hardhat.config.js像这样要求插件:
require("@nomiclabs/hardhat-waffle");
require('solidity-coverage');

/**
 * @type import('hardhat/config').HardhatUserConfig
 */

module.exports = {
  solidity:
"0.8.5",
};

现在hardhat知道激活了哪些插件并可以按需使用它们。但要真正使用它们,您首先需要创建一些命令。

 
添加命令
您的项目目前只知道一个命令:test,而这个命令甚至还没有真正实现。是时候改变这一点了。让我们添加命令来构建和测试您的智能合约:

"build": "hardhat compile",
"test:light": "hardhat test",
"test": "hardhat coverage",

这些命令将执行以下操作:

  1. build:这个命令告诉安全帽从contracts文件夹中取出你的 Solidity 文件并通过 Solidity 编译器运行它们。
  2. test:light:这个调用 Waffle 来测试你的合约。
  3. test:这个调用 Waffle 并另外为您生成一份覆盖率报告。覆盖命令由solidity-coverage 插件添加到Hardhat。

package.json修改后看起来像这样:

{
  "name": "solidity-project",
 
"version": "1.0.0",
 
"description": "",
 
"scripts": {
   
"build": "hardhat compile",
   
"test:light": "hardhat test",
   
"test": "hardhat coverage",
  },
 
"keywords": [],
 
"author": "Oliver Jumpertz",
 
"license": "ISC",
 
"devDependencies": {
   
"@nomiclabs/hardhat-ethers": "^2.0.2",
   
"@nomiclabs/hardhat-waffle": "^2.0.1",
   
"chai": "^4.3.4",
   
"ethereum-waffle": "^3.3.0",
   
"ethers": "^5.3.1",
   
"hardhat": "^2.3.3",
   
"solidity-coverage": "^0.7.16"
  }
}

现在您有了一个可以使用的基本项目。这意味着是时候在 Solidity 中实施您的第一个智能合约了,这样您就可以利用迄今为止投入到项目中的所有工作。
 

实施你的第一个智能合约
是时候写一些 Solidity 了!但是请不要期望太多。您将实施一个非常基本的合约,以便对 Solidity 有一个大致的了解。未来的文章肯定会涵盖更复杂的合约。
您可以在下面看到智能合约的完整代码。所以继续,复制代码,并将其放入contracts/MyContract.sol.

// SPDX-License-Identifier: MIT //这是一个许可证标题。Solidity 鼓励您许可甚至使您的代码开源,因为这与社区建立了信任。

//pragma是一个编译器指令。它指出这个特定的源文件至少需要 Solidity 编译器的 0.7.0 版本,并且不能与 0.9.0 以后的编译器版本一起使用。
//此语句的一件重要事情是:它仅对源文件是本地的。当您导入至少需要 Solidity 0.5.0 的文件时,这根本不会影响您的文件。您可能会在某些时候遇到编译器错误,因此请务必检查您引入的编译器版本库需要哪些。
pragma solidity >=0.7.0 <0.9.0;

contract MyContract {
    string private name;

    constructor(string memory _name) {
        name = _name;
    }

   
//这是一个setter方法。更准确地说,它是一个需要交易的函数,因为它修改name了合约的属性。该public关键字标记,这个函数是从外部访问。
         
//交易是被放入区块的交易。它们是矿工实际开采的。这也意味着:写入区块链的函数需要花钱才能执行!
    function changeName(string memory _name) public {
        name = _name;
    }

   
//这是一个getter方法。它不需要事务,因为它只读取数据。该public关键字使得这个功能从外部访问。该view关键字标记此功能为只读。而returns关键字标记在它之后来到括号内的返回类型。
         
//只读方法不需要任何费用。
    function getName() public view returns (string memory) {
        return name;
    }
}

现在你已经实现了你的第一个智能合约,是时候编译它了。
运行以下命令:
npm run build

您现在应该看到如下所示的输出:

Compiling 1 file with 0.8.5
Compilation finished successfully


如果您仔细观察您的项目,您可能会注意到artifacts已经创建了一个新文件夹。这是dist/out文件夹或您在常用项目中所称的任何内容。在此文件夹中,您会发现另外两个文件夹:contracts和MyContract.sol。在后者中,您会找到两个文件,名为MyContract.dbg.json和MyContract.json。特别是最后一个很有趣,因为它包含你的智能合约的 ABI 和字节码。这是 Solidity 编译器的输出。
如果您有兴趣,可以进一步查看或跟随以了解接下来如何测试您的合同。
 
测试你的合约
未经测试的软件总是有意外失败的风险。智能合约也不例外。但这正是我们包含 Waffle 和 chai 的目的,用于编写测试以验证您的合约是否按应有的方式工作。尤其是编写可以实际更新的智能合约并不简单(区块链上的所有内容都是不可变的,甚至是您的代码),因此测试更为重要。
Waffle 使您可以不使用 Solidity 本身而是使用 JavaScript 来测试您的智能合约,所有这些都在您可能已经熟悉的环境中进行。在底层,Waffle 使用 mocha 作为测试运行器,describe并it为 JavaScript 测试提供众所周知的结构。
查看以下代码,然后将其复制到一个新文件中test/MyContract.test.js:

const { expect } = require("chai");

describe(
"MyContract", () => {
  it(
"should return its name", async () => {
   
//这是一些魔法开始的地方。还记得你是如何加入[url=https://hashnode.com/@nomiclabs]@nomiclabs[/url] /hardhat-ethers 的吗?该插件将以太币全局注入到您的测试中,因此您不必导入它。
       
//指示 ethers 查找您的智能合约并创建一个工厂,以便您以后可以实例化它。
    const MyContract = await ethers.getContractFactory(
"MyContract");
   
//此行调用智能合约的构造函数。您在智能合约的构造函数中实现的所有内容现在都已执行。
    const myContract = await MyContract.deploy(
"My Contract");

    await myContract.deployed();
    expect(await myContract.getName()).to.equal(
"My Contract");
  });
  it(
"should change its name when requested", async () => {
    const MyContract = await ethers.getContractFactory(
"MyContract");
    const myContract = await MyContract.deploy(
"My Contract");

    await myContract.changeName(
"Another Contract");
    expect(await myContract.getName()).to.equal(
"Another Contract");
  });
});
 

执行你的测试
既然您的合同已包含测试,现在是运行它们并查看它们是否成功的时候了。
执行以下命令:
npm run test

您应该在终端中看到与此类似的输出:

> solidity-project@1.0.0 test
> hardhat coverage


Version
=======
> solidity-coverage: v0.7.16

Instrumenting for coverage...
=============================

> MyContract.sol

Compilation:
============

Compiling 1 file with 0.8.5
Compilation finished successfully

Network Info
============
> HardhatEVM: v2.3.3
> network:    hardhat



  MyContract
    should return its name (174ms)
    should change its name when requested (106ms)


  2 passing (283ms)

-----------------|----------|----------|----------|----------|----------------|
File             |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
-----------------|----------|----------|----------|----------|----------------|
 contracts/      |      100 |      100 |      100 |      100 |                |
  MyContract.sol |      100 |      100 |      100 |      100 |                |
-----------------|----------|----------|----------|----------|----------------|
All files        |      100 |      100 |      100 |      100 |                |
-----------------|----------|----------|----------|----------|----------------|

> Istanbul reports written to ./coverage/ and ./coverage.json

恭喜!您的测试通过 - 是时候考虑如何部署您的合约了。
 
部署你的合约
Solidity 不是在每台机器上运行的语言。它在作为以太坊节点一部分的以太坊虚拟机中运行。您的代码需要部署到区块链才能使用。
Hardhat 附带了一个任务运行器,这对自动化此任务有很大帮助。这些任务只不过是按需执行的 JavaScript 文件。
复制此代码并将其放入一个新文件中scripts/deployMyContract.js:
async function main() {
  const MyContract = await ethers.getContractFactory("MyContract");
  const myContract = await MyContract.deploy(
"My Contract");

  console.log(
"My Contract deployed to:", myContract.address);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

您可能会发现一些您之前在测试中已经见过的行。是的,您可以在此处使用您在测试中使用的相同实例化逻辑。
永远不要直接将您的合约部署到世界各地是一种很好的做法。很高兴,Hardhat 附带了一个本地测试网,您可以在其中测试您的合约并在本地使用它们。
将以下脚本添加到您的package.json:

"deploy:local": "hardhat run --network localhost scripts/deployMyContract.js"

该命令使用 Hardhat 执行您的脚本并将目标网络定义为 localhost。
接下来,您需要一个本地测试网,以便您可以在某个地方部署您的合约。
将另一个脚本添加到您的package.json:
"local-testnet": "hardhat node",

package.json现在应该类似:

{
  "name": "solidity-project",
 
"version": "1.0.0",
 
"description": "",
 
"scripts": {
   
"build": "hardhat compile",
   
"test:light": "hardhat test",
   
"test": "hardhat coverage",
   
"deploy:local": "hardhat run --network localhost scripts/deployMyContract.js",
   
"local-testnet": "hardhat node"
  },
 
"keywords": [],
 
"author": "Oliver Jumpertz",
 
"license": "ISC",
 
"devDependencies": {
   
"@nomiclabs/hardhat-ethers": "^2.0.2",
   
"@nomiclabs/hardhat-waffle": "^2.0.1",
   
"chai": "^4.3.4",
   
"ethereum-waffle": "^3.3.0",
   
"ethers": "^5.3.1",
   
"hardhat": "^2.3.3",
   
"solidity-coverage": "^0.7.16"
  }
}

您已准备好在本地部署合约所需的一切。现在打开一个新终端(选项卡)并执行以下命令:
npm run local-testnet
输出应如下所示:

> solidity-project@1.0.0 local-testnet
> hardhat node

Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/

Accounts
========
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

Account #2: 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc (10000 ETH)
Private Key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a

Account #3: 0x90f79bf6eb2c4f870365e785982e1f101e93b906 (10000 ETH)
Private Key: 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6

Account #4: 0x15d34aaf54267db7d7c367839aaf71a00a2c6a65 (10000 ETH)
Private Key: 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a

Account #5: 0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc (10000 ETH)
Private Key: 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba

Account #6: 0x976ea74026e726554db657fa54763abd0c3a0aa9 (10000 ETH)
Private Key: 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e

Account #7: 0x14dc79964da2c08b23698b3d3cc7ca32193d9955 (10000 ETH)
Private Key: 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356

Account #8: 0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f (10000 ETH)
Private Key: 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97

Account #9: 0xa0ee7a142d267c1f36714e4a8f75612f20a79720 (10000 ETH)
Private Key: 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6

Account #10: 0xbcd4042de499d14e55001ccbb24a551f3b954096 (10000 ETH)
Private Key: 0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897

Account #11: 0x71be63f3384f5fb98995898a86b02fb2426c5788 (10000 ETH)
Private Key: 0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82

Account #12: 0xfabb0ac9d68b0b445fb7357272ff202c5651694a (10000 ETH)
Private Key: 0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1

Account #13: 0x1cbd3b2770909d4e10f157cabc84c7264073c9ec (10000 ETH)
Private Key: 0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd

Account #14: 0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097 (10000 ETH)
Private Key: 0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa

Account #15: 0xcd3b766ccdd6ae721141f452c550ca635964ce71 (10000 ETH)
Private Key: 0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61

Account #16: 0x2546bcd3c84621e976d8185a91a922ae77ecec30 (10000 ETH)
Private Key: 0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0

Account #17: 0xbda5747bfd65f08deb54cb465eb87d40e51b197e (10000 ETH)
Private Key: 0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd

Account #18: 0xdd2fd4581271e230360230f9337d5c0430bf44c0 (10000 ETH)
Private Key: 0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0

Account #19: 0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199 (10000 ETH)
Private Key: 0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e

Hardhat 为您启动了一个本地以太坊网络并打印出所有现有帐户,包括他们的私钥。在你问之前:不,你不能把所有的以太币转移到主网上。抱歉,这只是用来玩和测试的假 Ether。
现在切换回原始终端(选项卡)并运行:
npm run deploy:local
这应该创建这样的输出:
> solidity-project@1.0.0 deploy:local
> hardhat run --network localhost scripts/deployMyContract.js

Compiling 1 file with 0.8.5
Compilation finished successfully
My Contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
`

恭喜,您已成功将第一个智能合约部署到本地测试网!
 
结论
就是这样。您已经建立了一个本地开发环境,实现了您的第一个智能合约,对其进行了测试,然后在本地进行了部署。
这个设置非常可靠,因为它带有维护良好的库和工具。它也很容易使用并且可以很好地扩展。您还可以非常轻松地扩展它,这要归功于 Hardhat 任务和 npm 的灵活性,可以在需要时简单地添加更多脚本。
你可以从这里继续玩。看看你还能创造什么。向该项目添加更多合约应该相对简单。
未来我肯定会涵盖更复杂的智能合约,从 ERC20,超过 ERC721,到 ERC1155,可升级合约(代理模式),甚至可能是完整的 DEX。所以请继续关注更多。