scaffold-eth 挑战2:创建ERC20代币及买卖合约
- 原文:how-to-create-an-erc20-token... 作者:Emanuele Ricci
- 译文出自:登链翻译计划
- 译者:翻译小组
- 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
接上一篇:我们创建了一个 ERC20 及使用 ETH 购买Token 的功能。现在我们进一步完善它。
这是练习的最后一部分,也是最难的一部分,不是从技术的角度,而是从概念和用户体验的角度。
我们希望允许用户将他们的代币卖给Vendor合约。如你所知,当合约的功能被声明为 payable
时,它可以接受ETH,但它只允许接受ETH。
我们现在需要实现:让Vendor直接从用户Token余额中提取Token,并回馈等值的ETH,这就是使用 授权机制
。
将发生的流程是这样:
approve
授权Vendor合约可将代币从用户钱包转移到Vendor(这是用户直接 调用Token合约)。当你调用approve
函数时,需要指定你想让被授权者能够转移的代币数量 ,(这里可设置为最大值)。sellTokens
函数,将用户的余额转移到Vendor的余额上。amount
为spender
可使用调用者的代币上的数量。函数返回一个布尔值,表示操作是否成功。函数触发Approval
事件。amount
代币从sender
移动到recipient
。amount
随后从调用者的授权的数量中扣除。返回一个布尔值,表示操作是否成功。发出一个transfer
事件。一个重要的说明,我想解释一下:用户体验及安全
这个授权机制并不是什么新东西,如果你曾经使用过像Uniswap这样的DEX,你已经做了这个。
approve函数允许其他钱包/合约最大限度地转移你在函数参数中指定的代币数量。这是什么意思?如果我想交易200个代币,我应该授权Vendor合约只能转移自己的200个代币。如果我想再次卖出100个,我则需要再次授权。这是一个好的用户体验吗?也许不是,但这是最安全的一个。
DEX使用另一种方法。为了避免每次把代币A换成代币B时都要求用户授权,DEX直接要求授权最大可能的代币数量。这意味着什么呢?每个DEX合约都有可能在你不知道的情况下偷走你所有的代币。你应该时刻注意幕后发生的事情!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "hardhat/console.sol";
import "./YourToken.sol";
// Learn more about the ERC20 implementation
// on OpenZeppelin docs: https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable
import "@openzeppelin/contracts/access/Ownable.sol";
contract Vendor is Ownable {
// Our Token Contract
YourToken yourToken;
// token price for ETH
uint256 public tokensPerEth = 100;
// Event that log buy operation
event BuyTokens(address buyer, uint256 amountOfETH, uint256 amountOfTokens);
event SellTokens(address seller, uint256 amountOfTokens, uint256 amountOfETH);
constructor(address tokenAddress) {
yourToken = YourToken(tokenAddress);
}
/**
* @notice Allow users to buy tokens for ETH
*/
function buyTokens() public payable returns (uint256 tokenAmount) {
require(msg.value > 0, "Send ETH to buy some tokens");
uint256 amountToBuy = msg.value * tokensPerEth;
// check if the Vendor Contract has enough amount of tokens for the transaction
uint256 vendorBalance = yourToken.balanceOf(address(this));
require(vendorBalance >= amountToBuy, "Vendor contract has not enough tokens in its balance");
// Transfer token to the msg.sender
(bool sent) = yourToken.transfer(msg.sender, amountToBuy);
require(sent, "Failed to transfer token to user");
// emit the event
emit BuyTokens(msg.sender, msg.value, amountToBuy);
return amountToBuy;
}
/**
* @notice Allow users to sell tokens for ETH
*/
function sellTokens(uint256 tokenAmountToSell) public {
// Check that the requested amount of tokens to sell is more than 0
require(tokenAmountToSell > 0, "Specify an amount of token greater than zero");
// Check that the user's token balance is enough to do the swap
uint256 userBalance = yourToken.balanceOf(msg.sender);
require(userBalance >= tokenAmountToSell, "Your balance is lower than the amount of tokens you want to sell");
// Check that the Vendor's balance is enough to do the swap
uint256 amountOfETHToTransfer = tokenAmountToSell / tokensPerEth;
uint256 ownerETHBalance = address(this).balance;
require(ownerETHBalance >= amountOfETHToTransfer, "Vendor has not enough funds to accept the sell request");
(bool sent) = yourToken.transferFrom(msg.sender, address(this), tokenAmountToSell);
require(sent, "Failed to transfer tokens from user to vendor");
(sent,) = msg.sender.call{value: amountOfETHToTransfer}("");
require(sent, "Failed to send ETH to the user");
}
/**
* @notice Allow the owner of the contract to withdraw ETH
*/
function withdraw() public onlyOwner {
uint256 ownerBalance = address(this).balance;
require(ownerBalance > 0, "Owner has not balance to withdraw");
(bool sent,) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send user balance back to the owner");
}
}
让我们回顾一下sellTokens
:
首先,检查tokenAmountToSell
是否大于0
,否则,我们回退交易。你需要至少卖出1代币!
然后我们检查用户的代币余额至少大于他试图出售的代币数量,你不能超额出售你不拥有的东西!
之后,我们计算卖出操作后给用户的ETH数量 AmountOfETHToTransfer
。我们需要确定Vendor能够支付这个金额,所以我们要检查Vendor的余额(以ETH为单位)是否大于要转账给用户的金额。
如果一切正常,我们就进行(bool sent) = yourToken.transferFrom(msg.sender, address(this), tokenAmountToSell);
操作。我们告诉YourToken合约将tokenAmountToSell
从用户msg.sender
的余额转移到Vendor地址 address(this)
。
最后要做的是将ETH金额转回用户的地址。然后我们就完成了!
为了在React应用程序中进行测试,你可以更新App.jsx,添加两个Card
进行Approve
和Sell
代币(见文章末尾的GitHub代码库),或者你可以直接从调试合约标签中进行所有操作,它提供了所有需要的功能。
https://www.youtube.com/watch?v=G1Wcb6Q3mYI
从之前文章中你已经知道,测试是应用程序的安全和优化的一个重要基础。你不应该跳过它们,Solidity 环境下的测试利用了四个库:
这个测试将验证我们的 sellTokens
函数是否按预期工作。
让我们回顾一下逻辑:
addr1
从Vendor合约中购买一些代币。sellTokens
函数来出售刚刚买到的代币数量。在这一点上,我们需要检查三件事:
Waffle提供了一些很酷的工具来检查以太坊余额的变化和代币余额的变化,但不幸的是,后者似乎有一个问题(请查看我刚刚创建的GitHub问题)。
const {ethers} = require('hardhat');
const {use, expect} = require('chai');
const {solidity} = require('ethereum-waffle');
use(solidity);
describe('Staker dApp', () => {
let owner;
let addr1;
let addr2;
let addrs;
let vendorContract;
let tokenContract;
let YourTokenFactory;
let vendorTokensSupply;
let tokensPerEt...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!