Solidity 智能合约调用 ERC-20 代币:转账与授权的完整实战

·

「去中心化交易所 (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;

项目包含两份合约:

  1. ERC20Basic:最小原理级 ERC-20 代币(模拟代币发行)。
  2. 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_;
    }
    ...
}

实现重点


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);
}

核心要点

交互示例(Brownie/Foundry 均可)

dex = DEX.deploy({'from': account1})
dex.buy({'from': account2, 'value': 1e18})

用户需支付 1 ETH;链上 TransferBought 事件一次打出。


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);
}

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 地址,提高复用性。


进一步提升:从“测试版本”迈向“生产级”

  1. 引入 CI/CD + Hardhat 自动化测试所有转账流。
  2. 使用 OpenZeppelin IERC20 + ReentrancyGuard 避免重入风险。
  3. 部署后监听链上事件,前端实时刷新余额。

👉 深入学习监听事件的高级技巧与 AB i解码一步到位!


完整源码速查

上方章节已给出核心片段。如需完整脚本,可直接粘贴以下两合约并部署到 Remix、Hardhat 或 Brownie,无需改动即可在本地网络跑通 买/卖 全流程。

祝你编码愉快,下次我们把 流动性池滑点保护前端钱包交互 一次打包讲全!