在之前的教程中,我们已经深入探讨了基于 Solidity 的 ERC-20 代币智能合约的结构与原理。本文将继续引导您如何通过 Solidity 编写的智能合约,实现与 ERC-20 代币的交互,特别是代币的转移(Transfer)和批准(Approve)操作。
我们将通过构建一个简化的去中心化交易所(DEX)智能合约为例,演示用户如何使用 ETH 购买代币,以及如何将代币出售换回 ETH。这一过程不仅涉及基本的转账操作,还包括权限控制与余额检查等关键机制。
ERC-20 接口与去中心化交易所结构
在编写 DEX 合约之前,我们需要先定义 ERC-20 代币的接口(Interface),以便合约能够识别并调用代币的标准方法。以下是一个符合 ERC-20 标准的接口定义:
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}我们的 DEX 合约将使用这一接口与代币合约进行交互。合约结构包括代币实例、购买函数和出售函数:
contract DEX {
IERC20 public token;
event Bought(uint256 amount);
event Sold(uint256 amount);
constructor() {
token = new ERC20Basic();
}
function buy() payable public {
// 购买逻辑
}
function sell(uint256 amount) public {
// 出售逻辑
}
}实现代币购买函数
在 buy 函数中,用户通过发送 ETH 来购买代币。合约需要检查用户支付的 ETH 数量,并确保合约储备中有足够的代币可用于交易:
function buy() payable public {
uint256 amountTobuy = msg.value;
uint256 dexBalance = token.balanceOf(address(this));
require(amountTobuy > 0, "You need to send some ether");
require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
token.transfer(msg.sender, amountTobuy);
emit Bought(amountTobuy);
}此处假设 1 Wei 对应 1 个代币,实际应用中可能需要更复杂的定价机制。如果购买成功,合约将触发 Bought 事件,并将代币转移给用户。
实现代币出售函数
出售代币的过程稍复杂。用户需要先调用代币合约的 approve 方法,授权 DEX 合约从其账户中转出特定数量的代币。之后,用户才能调用 DEX 的 sell 函数完成出售操作:
function sell(uint256 amount) public {
require(amount > 0, "You need to sell at least some tokens");
uint256 allowance = token.allowance(msg.sender, address(this));
require(allowance >= amount, "Check the token allowance");
token.transferFrom(msg.sender, address(this), amount);
payable(msg.sender).transfer(amount);
emit Sold(amount);
}此函数会检查用户授权的额度是否足够,然后执行代币转账并向用户发送相应的 ETH。
完整合约代码示例
以下为完整的 DEX 合约及相关的 ERC-20 代币合约代码,供您参考与测试:
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract ERC20Basic is IERC20 {
string public constant name = "ERC20Basic";
string public constant symbol = "ERC";
uint8 public constant decimals = 18;
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowed;
uint256 totalSupply_ = 10 ether;
constructor() {
balances[msg.sender] = totalSupply_;
}
function totalSupply() public override view returns (uint256) {
return totalSupply_;
}
function balanceOf(address tokenOwner) public override view returns (uint256) {
return balances[tokenOwner];
}
function transfer(address receiver, uint256 numTokens) public override returns (bool) {
require(numTokens <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender] - numTokens;
balances[receiver] = balances[receiver] + numTokens;
emit Transfer(msg.sender, receiver, numTokens);
return true;
}
function approve(address delegate, uint256 numTokens) public override returns (bool) {
allowed[msg.sender][delegate] = numTokens;
emit Approval(msg.sender, delegate, numTokens);
return true;
}
function allowance(address owner, address delegate) public override view returns (uint) {
return allowed[owner][delegate];
}
function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {
require(numTokens <= balances[owner]);
require(numTokens <= allowed[owner][msg.sender]);
balances[owner] = balances[owner] - numTokens;
allowed[owner][msg.sender] = allowed[owner][msg.sender] - numTokens;
balances[buyer] = balances[buyer] + numTokens;
emit Transfer(owner, buyer, numTokens);
return true;
}
}
contract DEX {
event Bought(uint256 amount);
event Sold(uint256 amount);
IERC20 public token;
constructor() {
token = new ERC20Basic();
}
function buy() payable public {
uint256 amountTobuy = msg.value;
uint256 dexBalance = token.balanceOf(address(this));
require(amountTobuy > 0, "You need to send some ether");
require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");
token.transfer(msg.sender, amountTobuy);
emit Bought(amountTobuy);
}
function sell(uint256 amount) public {
require(amount > 0, "You need to sell at least some tokens");
uint256 allowance = token.allowance(msg.sender, address(this));
require(allowance >= amount, "Check the token allowance");
token.transferFrom(msg.sender, address(this), amount);
payable(msg.sender).transfer(amount);
emit Sold(amount);
}
}常见问题
什么是 ERC-20 代币?
ERC-20 是以太坊上的一种代币标准,定义了代币合约必须实现的一组接口和方法,包括转账、查询余额、授权等操作。该标准确保了不同代币之间的互操作性。
为什么在出售代币前需要调用 approve 函数?
approve 函数用于授权另一个地址(如 DEX 合约)从用户账户中转出特定数量的代币。这是 ERC-20 标准的安全机制,防止未经授权的资金转移。
如何检查代币交易是否成功?
交易成功后,合约会触发相应的事件(如 Transfer、Bought、Sold)。您可以通过监听这些事件,或直接查询用户和合约的代币余额变化来确认交易结果。
是否可以在其他网络上部署此类合约?
可以。只要网络支持以太坊虚拟机(EVM)和 Solidity 智能合约,如 BSC、Polygon 等,均可部署和运行 ERC-20 代币及 DEX 合约。
如何扩展本示例中的 DEX 功能?
实际应用中,您可以添加更多功能,如流动性池、自动化做市机制(AMM)、手续费扣除等,以构建更完善的去中心化交易平台。
遇到“Not enough tokens in the reserve”错误怎么办?
该错误表示合约储备代币不足。需确保合约地址持有足够代币,或调整交易数量。如果是测试环境,可先向合约地址转入代币。
通过本文,您应已了解如何使用 Solidity 智能合约实现 ERC-20 代币的转移与批准操作。无论是构建简单的去中心化交易所,还是集成代币功能到更大项目中,这些基础知识都至关重要。