掌握 Solidity 智能合约中的 ERC-20 代币转移与批准操作

·

在之前的教程中,我们已经深入探讨了基于 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 标准的安全机制,防止未经授权的资金转移。

如何检查代币交易是否成功?

交易成功后,合约会触发相应的事件(如 TransferBoughtSold)。您可以通过监听这些事件,或直接查询用户和合约的代币余额变化来确认交易结果。

是否可以在其他网络上部署此类合约?

可以。只要网络支持以太坊虚拟机(EVM)和 Solidity 智能合约,如 BSC、Polygon 等,均可部署和运行 ERC-20 代币及 DEX 合约。

如何扩展本示例中的 DEX 功能?

实际应用中,您可以添加更多功能,如流动性池、自动化做市机制(AMM)、手续费扣除等,以构建更完善的去中心化交易平台。

遇到“Not enough tokens in the reserve”错误怎么办?

该错误表示合约储备代币不足。需确保合约地址持有足够代币,或调整交易数量。如果是测试环境,可先向合约地址转入代币。


通过本文,您应已了解如何使用 Solidity 智能合约实现 ERC-20 代币的转移与批准操作。无论是构建简单的去中心化交易所,还是集成代币功能到更大项目中,这些基础知识都至关重要。