「去中心化交易所 (DEX)」、「ERC-20 代币接口」、「Solidity 智能合约开发」等关键词将贯穿全文,帮助你快速掌握如何在 Ethereum 网络上让一个智能合约安全地完成「买币」「卖币」整套闭环操作。
前置知识回顾:为什么需要 ERC-20 接口?
在前一篇文章里,我们拆解了 ERC-20 合约的六大必选函数: totalSupply balanceOf allowance transfer approve transferFrom。
现实中的 DEX、钱包或其它 DeFi 合约并不会重复造轮子,而是通过 接口定义 的方式复用任意代币的“账本逻辑”。这份教程将带你把接口真正地用到业务场景:创建一个迷你去中心化交易所,让用户用 ETH 兑换我们刚部署的 ERC-20 代币。
快速搭建环境:代码结构总览
pragma solidity ^0.8.0;项目包含两份合约:
ERC20Basic:最小原理级 ERC-20 代币(模拟代币发行)。DEX:迷你去中心化交易所(业务逻辑)。
Contract 1:ERC20Basic 合约精要
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
...
}
contract ERC20Basic is IERC20 {
string public constant name = "ERC20Basic";
string public constant symbol = "ERC";
uint8 public constant decimals = 18;
uint256 totalSupply_ = 10 ether; // 10 亿 Wei
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowed;
constructor() {
balances[msg.sender] = totalSupply_;
}
...
}实现重点
- 构造函数一次性把 totalSupply_ 全额转给部署者地址。
approve和transferFrom组合解决「代付」授权场景——DEX 需要先得到 owner 的授权,才能执行 sell 流程。
Contract 2:DEX 合约全流程
contract DEX {
IERC20 public token;
event Bought(uint256 amount);
event Sold(uint256 amount);
constructor() {
token = new ERC20Basic(); // DEX 立刻铸造 10 ether 储备
}
}2.1 buy():用 ETH 买代币
function buy() payable public {
uint256 amountToBuy = msg.value; // 例:1 ETH = 1e18 Wei
uint256 dexBalance = token.balanceOf(address(this));
require(amountToBuy > 0, "Need some ether");
require(amountToBuy <= dexBalance, "Reserve too low");
token.transfer(msg.sender, amountToBuy);
emit Bought(amountToBuy);
}核心要点
- 不进行复杂定价逻辑,保持 1:1 简单映射关系。
- revert 自动退回主网资产,安全资金路。
⚡ 交互示例(Brownie/Foundry 均可)
dex = DEX.deploy({'from': account1})
dex.buy({'from': account2, 'value': 1e18})用户需支付 1 ETH;链上 Transfer 与 Bought 事件一次打出。
2.2 sell():卖掉代币换回 ETH
先在客户端授权,才能在链上调用 sell:
token = ERC20Basic.at(dex.token())
token.approve(dex.address, 3e18, {'from': account2})function sell(uint256 amount) public {
require(amount > 0, "Need some tokens");
uint256 allowance = token.allowance(msg.sender, address(this));
require(allowance >= amount, "Approve first");
token.transferFrom(msg.sender, address(this), amount);
payable(msg.sender).transfer(amount);
emit Sold(amount);
}allowance防止用户在未授权时批量调用,保护资金安全。- 事件双射:看到
Transfer和Sold,即可完成余额对账。
FAQ | 常见疑点 5 连击
Q1:为什么不直接 transfer,非要 approve + transferFrom?
A:使用 transfer 是资产“主动打赏”——对方无需授权;而 transferFrom 解决“他人代付”,必须提前在 allowed[owner][DEX] 留下额度,DEX 才能合法转移。
Q2:buy 合约永远只能 1:1 兑换吗?
A:教程为了聚焦 转账与授权 接口,用最简单位 1:1。你可以在继承层加入 定价曲线或预言机 拓展成交算法。
Q3:DEX 会耗尽我的代币余额吗?
A:只要 require(amountToBuy <= dexBalance) 前置条件健在,DEX 永远不会超卖,合约兜底保护 🛡。
Q4:如果 sell 回退失败会发生什么?
A:Solidity 异常回滚机制会全链路撤销交易,用户代币仍在钱包,不会产生资产损失。
Q5:为什么合约要自己 new ERC20Basic(),而不是引用外部已部署代币?
A:教程模拟单一测试场景。真实 DEX 建议通过构造函数或 setter 传入任意 ERC-20 地址,提高复用性。
进一步提升:从“测试版本”迈向“生产级”
- 引入 CI/CD + Hardhat 自动化测试所有转账流。
- 使用 OpenZeppelin IERC20 + ReentrancyGuard 避免重入风险。
- 部署后监听链上事件,前端实时刷新余额。
完整源码速查
上方章节已给出核心片段。如需完整脚本,可直接粘贴以下两合约并部署到 Remix、Hardhat 或 Brownie,无需改动即可在本地网络跑通 买/卖 全流程。
祝你编码愉快,下次我们把 流动性池、滑点保护 与 前端钱包交互 一次打包讲全!