如何创建 ERC20 代币和 Solidity 供应商合约来买卖自己的代币
在之前的 scaffold-eth 挑战中,我们创建了一个 Staker dApp。在本次挑战中,我们将创建一个 Token Vendor 合约。
dApp 的目标
此挑战的目标是创建您自己的 ERC20 代币和代币供应商合同,该合同将处理您的代币的买卖过程,并将其与用户发送的 ETH 进行交换。
你要学什么?
- 什么是 ERC20 代币
- 如何铸造 ERC20 代币
- OpenZeppelin ERC20 实施
- 合同所有权
- 如何创建代币供应商合约来出售/购买你的代币
除了上述内容外,我们还将学习许多新的 Solidity 和 web3 概念,以及如何为你的 Solidity 代码编写完善的测试。我将跳过一些基础部分,所以如果你感到困惑,只需回到第一篇挑战博客文章并阅读所有解释即可。
您应该始终牢记一些始终有用的链接:
- Solidity 示例
- Solidity 文档
- 安全帽文档
- Ethers-js 文档
- OpenZeppelin 文档
- OpenZeppelin Ethernaut 教程
- CryptoZombies 教程
什么是 ERC20 代币?
在我们开始之前,我将直接引用以太坊文档,向您概述一下 ERC20 代币的含义。
代币几乎可以代表以太坊中的任何东西:
- 在线平台的声誉点
- 游戏中角色的技能
- 彩票
- 金融资产,例如公司股份
- 美元等法定货币
- 一盎司黄金
- 以及更多……
以太坊如此强大的功能,必然需要一个强大的标准来支撑,对吧?这正是 ERC-20 发挥作用的地方!该标准允许开发者构建可与其他产品和服务互操作的代币应用程序。
ERC-20 引入了可替代代币 (Fungible Tokens) 的标准,换句话说,它们具有一种属性,使得每个代币 (在类型和价值上) 都与另一个代币完全相同。例如,一个 ERC-20 代币的行为与 ETH 相同,这意味着 1 个代币始终等于所有其他代币。
如果您想了解有关 ERC-20 代币的更多信息,可以查看以下链接:
设置项目
首先,我们需要进行设置。克隆 scaffold-eth 仓库,切换到 challenge 1 分支,并安装所有需要的依赖项。
git clone [https://github.com/austintgriffith/scaffold-eth.git](https://github.com/austintgriffith/scaffold-eth.git) challenge-2-token-vendor
cd challenge-2-token-vendor
git checkout challenge-2-token-vendor
yarn install
在本地测试您的应用程序
yarn chain
开设本地安全帽连锁店yarn start
启动本地 React 应用yarn deploy
部署/重新部署你的合约并更新 React 应用
OpenZeppelin 和 ERC20 实施
OpenZeppelin 提供安全产品来构建、自动化和操作分散式应用程序。
我们将使用 OpenZeppelin 合约框架来构建我们自己的 ERC20 代币。
该框架是一个用于安全智能合约开发的库。它建立在经过社区审查的坚实代码基础上。
- ERC20和ERC721等标准的实现。
- 灵活的基于角色的权限方案。
- 可重复使用的Solidity 组件来构建自定义合约和复杂的分散系统。
如果您想了解有关 OpenZeppelin 实现的更多信息,可以点击以下链接:
练习第 1 部分:创建您自己的 ERC20 代币并部署它!
在练习的第一部分,您需要创建一个继承自 OpenZepllein 的 ERC20 合约的代币合约。
在构造函数中,您必须铸造1000 token
(记住在 Solidity 中ERC20 代币有 18 位小数)并将它们发送给msg.sender
(部署合约的那个)。
记得更新deploy.js
文件,将这些代币发送到正确的地址。你可以在网页应用程序的右上角找到你当前的地址,点击复制图标即可!
要将代币转移到您的帐户,请将此行添加到您的deploy.js
:
const result = await yourToken.transfer("**YOUR FRONTEND ADDRESS**", utils.parseEther("1000"));
别害怕,我稍后查看代码后会解释。
- 你能在前端看到
balanceOf
你的钱包里有这 1000 个代币吗? - 你能把
transfer()
其中一些代币转到另一个钱包地址吗?只需在 Chrome 上打开一个新的隐身窗口,输入你的本地主机地址,你就应该有一个全新的刻录账户来发送这些代币!
需要掌握的重要概念
- OpenZeppelin ERC20 合约
- 以太坊 ERC-20 标准
- 继承 ——合约可以使用
is
关键字从其他合约继承。 - 隐藏继承的状态变量 ——正如 SolidityByCode 所解释的,与函数不同,状态变量不能通过在子合约中重新声明来覆盖
你的代币.sol
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.4; | |
// Learn more about the ERC20 implementation | |
// on OpenZeppelin docs: https://docs.openzeppelin.com/contracts/4.x/erc20 | |
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
contract YourToken is ERC20 { | |
constructor() ERC20("Scaffold ETH Token", "SET") { | |
_mint(msg.sender, 1000 * 10 ** 18); | |
} | |
} |
如您所见,我们正在从 OpenZeppelin 库导入 ERC20.sol 合约。该合约是 OpenZeppelin 对 ERC20 标准的实现,他们在安全性和优化方面都做得非常出色!
当您在代码中is ERC20
编写代码时,您的YourContract
合约将继承 OpenZeppelin 中 ERC20 合约中实现的所有函数/状态变量。
令人惊奇的是,一切都是开源的。尝试CMD+click
一下 ERC20 关键字或函数_mint
。
如您所见,当constructor
我们的合约被调用时,我们也会调用 ERC20 构造函数并传递两个参数。第一个是name
我们的代币的,第二个是的symbol
。
第二个重要部分是_mint
功能,我们来看一下。
您首先require
看到的只是检查铸币者(将接收所有铸造的代币的人)不是空地址。
_beforeTokenTransfer
和_afterTokenTransfer
是在任何代币转移后调用的函数钩子。这包括铸造和销毁。
在其余代码中,我们正在更新代_totalSupply
币(在我们的例子中,它将是 1000 个代币,带有 18 位小数),balance
使用金额更新铸币者,并且我们正在发出一个Transfer
事件。
这有多酷?而且我们TokenContract
只调用了一个函数。
还记得我说过要更新 deploy.js 文件,以便将所有代币转移到 Web 应用中的钱包吗?代码如下:
await yourToken.transfer(‘0xafDD110869ee36b7F2Af508ff4cEB2663f068c6A’, utils.parseEther(‘1000’));
transfer
是 ERC20 合约实现提供的另一项功能。
我不会讲太多细节,但在检查sender
和recipient
不是之后,null address
该函数将检查发送者是否有足够的余额来转移请求的金额,将转移它并发出一个Transfer
事件。
练习第 2 部分:创建供应商合同
在本部分练习中,我们将创建供应商合同。
供应商将负责允许用户将 ETH 兑换成我们的代币。为此,我们需要
- 为我们的代币设定价格(1 ETH = 100 代币)
- 实现一个可支付
buyToken()
函数。要转移代币,请查看transfer()
OpenZeppelin ERC20 实现中公开的函数。 - 发出一个
BuyTokens
事件,记录买家是谁、发送的 ETH 数量以及购买的代币数量 - 在部署时将所有代币转移到供应商合约
- 将供应商合约(在部署时)转移
ownership
到我们的前端地址(您可以在 Web 应用程序的右上角看到它)以提取余额中的 ETH
需要掌握的重要概念
- 活动
- 应付函数
- Open Zeppelin 可拥有和所有权 - 通过继承使用的 OpenZeppelin 模块。它将提供修饰符
onlyOwner
,可将其应用于您的函数,以将其使用限制为所有者。 - OpenZeppelin 地址实用程序(非必需,但有用)——与地址类型相关的函数集合。你可以使用它安全地将 ETH 资金从供应商转移到所有者。
- OpenZeppelin ERC20 合约的转移函数 —— 将代币从调用者的账户
transfer(address recipient, uint256 amount)
转移到并返回一个布尔值,指示操作是否成功。amount
recipient
- 发送以太币 ——正如我们在上一个挑战中看到的那样,总是使用
call
函数来做到这一点!
供应商.sol
让我们回顾一下代码的重要部分。
在 中,buyTokens()
我们会检查用户是否至少向我们发送了一些 ETH,否则我们将撤销该交易(别吝啬!)。请记住,为了接收 ETH,我们的函数必须包含关键字payable
。
之后,我们根据代币价格计算出他发送的 ETH 数量可以兑换多少代币。
我们还检查供应商合同是否有足够的代币余额来满足用户的购买请求,否则我们将撤销交易。
如果所有检查都顺利,我们就会触发transfer
代币合约中实现的函数,该函数由代币合约继承,该合约在 ERC20 合约中实现(代码见上图)。该函数会返回一个 ,boolean
用于通知我们操作是否成功。
最后要做的事情是发出BuyTokens
事件来通知区块链我们达成了交易!
这个withdraw()
函数非常简单。正如你所见,它依赖于onlyOwner
function modifier
我们从合约中继承的Owner
。该修饰符用于检查 是否msg.sender
是合约的所有者。我们不希望其他用户提取我们收集的 ETH。在函数内部,我们将 ETH 转移给所有者,并检查操作是否成功。另一种方法是,正如我之前所说,使用OpenZeppelin Address 实用程序sendValue
的。
练习第 3 部分:允许供应商回购!
这是练习的最后一部分,也是最困难的部分,不是从技术的角度,而是从概念和用户体验的角度。
我们希望允许用户将其代币出售给我们的供应商合约。如您所知,当合约的函数声明为 时,合约可以接受 ETH payable
,但合约只能接收 ETH。
因此,我们需要实现的是允许供应商直接从我们的代币余额中提取代币,并相信他们会返还等值的 ETH。这被称为“Approve 方法”。
这是将要发生的流程:
- 用户请求“批准”供应商合约将代币从用户余额转移到供应商钱包(这将在代币合约上进行)。调用该函数时,您需要指定允许另一个合约最多
approve
转移的代币数量。 - 用户将调用
sellTokens
供应商合约上的一个函数,将用户的余额转移到供应商的余额 - 供应商的合约将向用户的钱包转移等量的 ETH
需要掌握的重要概念
- approve ERC20 函数 ——设置调用者代币
amount
的限额spender
。返回布尔值,指示操作是否成功。触发[Approval](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#IERC20-Approval-address-address-uint256-)
事件。 - transferFrom ERC20 函数 ——使用限额机制将
amount
代币从转移sender
到。然后从调用者的限额中扣除。返回布尔值,指示操作是否成功。触发事件。recipient
amount
[Transfer](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#IERC20-Transfer-address-address-uint256-)
我想解释一下:用户体验高于安全性。
这种批准机制并不新鲜。如果你曾经使用过像 Uniswap 这样的 DEX,那么你已经这样做了。approve 函数允许其他钱包/合约最多转账你在函数参数中指定的代币数量。这是什么意思?如果我想交易 200 个代币,我应该批准 Vendor 合约只向自己转账 200 个代币。如果我想再卖出 100 个,我应该再次批准。这样的用户体验好吗?可能不好,但它是最安全的。
DEX 采用了另一种方法。为了避免每次用户想要将 TokenA 兑换成 TokenB 时都要求用户批准,他们直接请求用户批准最大数量的代币。这意味着什么?每个 DEX 合约都可能在你不知情的情况下窃取你所有的代币。你应该时刻关注幕后发生的事情!
供应商.sol
我们来回顾一下sellTokens
。
首先,我们检查是否tokenAmountToSell
大于0
,否则,我们将撤销交易。您需要至少出售一个代币!
然后,我们会检查用户的代币余额是否至少大于他想要出售的代币数量。您不能超卖您不拥有的代币!
之后,我们会计算amountOfETHToTransfer
出售操作后支付给用户的金额。我们需要确保供应商能够支付该金额,因此我们会检查供应商的余额(以 ETH 为单位)是否大于需要支付给用户的金额。
如果一切正常,我们继续(bool sent) = yourToken.transferFrom(msg.sender, address(this), tokenAmountToSell);
操作。我们告诉 YourToken 合约将tokenAmountToSell
资金从用户余额转移msg.sender
到供应商余额address(this)
。只有当用户已经 通过我们之前审核过的函数批准了至少该特定金额时,此操作才能成功。approve
最后一步,将出售操作所需的 ETH 金额转回用户地址。大功告成!
更新你的 App.jsx
为了在您的 React 应用程序中测试这一点,您可以更新您的 App.jsx 并添加两个Card
和Approve
令牌Sell
(请参阅文章末尾的 GitHub 代码库),或者您可以从提供所有所需功能的“调试合同”选项 卡中执行所有操作。
练习第 4 部分:创建测试套件
你已经从上一篇文章中了解到,测试是应用程序安全性和优化的重要基础。你绝对不能忽略它们,它们是理解整个应用程序逻辑中涉及的操作流程的一种方式。
Solidity 环境测试利用了四个库:
让我们回顾一个测试,然后我将转储整个代码
测试 sellTokens() 函数
这是验证我们的sellTokens
功能是否按预期工作的测试。
我们来回顾一下逻辑:
- 首先
addr1
从供应商合约中购买一些代币 - 正如我们之前所说,在出售之前,我们需要批准供应商合同,以便能够将我们想要出售的代币数量转移给自己。
- 批准后,我们会再次检查供应商在 addr1 中持有的代币限额是否至少等于 addr1 需要出售(并转移给供应商)的代币数量。这项检查可以跳过,因为我们知道 OpenZeppeling 已经对其代码进行了全面测试,但我只是想出于学习目的添加它。
sellTokens
我们准备使用供应商合约的功能出售我们刚刚购买的代币数量
此时我们需要检查三件事:
- 用户的代币余额为 0(我们已售出所有代币)
- 用户的钱包因该交易增加了 1 ETH
- 供应商的代币余额为 1000(我们购买了 100 个代币)
Waffle 提供了一些很酷的实用程序来检查以太币余额的变化和代币余额的变化,但不幸的是,后者似乎存在一个问题(查看我刚刚创建的 GitHub 问题)。
测试覆盖完整代码
最后一步:将您的合约部署到月球(测试网)
好的,现在是时候了。我们已经实现了智能合约,测试了前端 UI,并且测试涵盖了所有边缘情况。我们已准备好在测试网上部署它。
按照scaffold-eth 文档,我们需要遵循以下步骤:
defaultNetwork
将in更改packages/hardhat/hardhat.config.js
为您想要使用的测试网(在我的情况下是 rinkeby)infuriaProjectId
使用在Infura上创建的更新- 生成部署账户
with yarn generate
。此命令应生成两个.txt
文件。一个代表账户地址,另一个代表所生成账户的种子短语。 - 运行
yarn account
即可查看帐户的详细信息,例如不同网络的 eth 余额。 - 确保mnemonic.txt和相关帐户文件未通过您的 git 存储库推送,否则任何人都可以获得您的合约的所有权!
- 为你的部署者账户充值一些资金。你可以使用即时钱包将资金发送到你刚刚在控制台上看到的二维码。
- 使用 部署您的合约
yarn deploy
!
如果一切顺利的话你应该会在控制台上看到类似这样的内容
部署元数据存储在文件夹中,并 通过命令中的标志
_/deployments_
自动复制到 (参见)。_/packages/react-app/src/contracts/hardhat_contracts.json_
_--export-all_
_yarn deploy_
_/packages/hardhat/packagen.json_
如果您想检查已部署的合约,您可以在 Etherscan Rinkeby 网站上搜索它们:
更新您的前端应用程序并将其部署在 Surge 上!
我们将使用Surge方法,但您也可以在AWS S3或IPFS上部署您的应用程序,这取决于您!
scaffold -eth 文档总是随手可得,但我会总结一下你应该做的事情:
- 如果您要在主网上部署,则应该在 Etherscan 上验证您的合约。此过程将增强您应用程序的可信度和信任度。如果您有兴趣,请遵循scaffold-eth指南。
- 关闭调试模式(它会打印大量的 console.log,相信我,这可不是你想在 Chrome 开发者控制台中看到的!)。打开
App.jsx
,找到const DEBUG = true;
并将其转换为false
。 - 查看
App.jsx
并删除所有未使用的代码,确保只发送您真正需要的代码! - 确保你的 React 应用指向正确的网络(即你刚刚用于部署合约的网络)。查找
const targetNetwork = NETWORKS[“localhost”];
并替换localhost
为你的合约的网络。在本例中,它将是rinkeby
- 确保你使用的是自己的节点,而不是 Scaffold-eth 中的节点,因为它们是公开的,并且无法保证它们会被关闭或限制速率。请查看第 58 行和第 59 行
App.jsx
- 如果您想使用他们的服务,请更新
constants.js
并交换Infura、Etherscan和Blocknative API 密钥。
准备好了吗?出发!
现在使用 构建你的 React App yarn build
,当构建脚本完成后,使用 将其部署到 Surge yarn surge
。
如果一切顺利,你应该会看到类似这样的内容。你的 dApp 现已在 Surge 上线!
您可以在这里查看我们部署的 dApp:https://woozy-cable.surge.sh/
回顾与结论
这就是我们迄今为止学到的和做的
- 克隆 scaffold-eth 挑战 repo
- 学习了很多 web3/solidity 概念(深入研究 ERC20 合约、批准模式等)
- 创建 ERC20 代币合约
- 创建供应商合同以允许用户购买和出售它们
- 在安全帽网络上本地测试了我们的合约
- 在 Rinkeby 上部署我们的合约
- 在 Surge 上部署了我们的 dApp
如果一切按预期进行,您已准备好实现重大飞跃并将所有内容部署到以太坊主网上!
该项目的 GitHub Repo:scaffold-eth-challenge-2-token-vendor
你喜欢这篇内容吗?关注我,了解更多!
- GitHub:https://github.com/StErMi
- 推特: https: //twitter.com/StErMi
- 中等:https://medium.com/@stermi
- Dev.to:https://dev.to/stermi