区块链-ERC20代币发行逻辑详解

前言

我们对erc20代币相关的内容做一些讲解与总结

功能

代币的元数据

typescript 复制代码
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;

name:代币名称(例如 "Manual Token")。

symbol:代币符号(例如 "MTK")。

decimals:小数位,默认 18,符合主流 ERC20 代币习惯。

totalSupply:代币总供应量。

余额与授权机制

ini 复制代码
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

balanceOf[address]:查询某个地址的代币余额。

allowance[owner][spender]:记录某个地址(owner)允许另一个地址(spender)代扣的额度。

事件

csharp 复制代码
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
event Burn(address indexed from, uint256 value);

Transfer:记录转账。

Approval:记录授权。

Burn:记录代币销毁。

构造函数

ini 复制代码
constructor(uint256 initialSupply, string memory tokenName, string memory tokenSymbol) {
    totalSupply = initialSupply * 10 ** uint256(decimals);
    balanceOf[msg.sender] = totalSupply;
    name = tokenName;
    symbol = tokenSymbol;
}

部署时传入初始供应量、代币名称、符号。

代币总量自动分配给部署者地址。

内部转账函数 _transfer

ini 复制代码
function _transfer(address _from, address _to, uint256 _value) internal {
    require(_to != address(0x0));
    require(balanceOf[_from] >= _value);
    require(balanceOf[_to] + _value >= balanceOf[_to]);
    uint256 previousBalances = balanceOf[_from] + balanceOf[_to];
    balanceOf[_from] -= _value;
    balanceOf[_to] += _value;
    emit Transfer(_from, _to, _value);
    assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}

防止转账到 0x0(除非销毁)。

检查余额是否足够。

防止溢出。

转账前后总和不变,保证会计一致性。

转账授权功能

transfer:从自己账户转账,最后调用_transfer函数。

php 复制代码
    function transfer(
        address _to,
        uint256 _value
    ) public returns (bool success) {
        _transfer(msg.sender, _to, _value);
        return true;
    }

transferFrom:从他人账户转账(前提是该地址被允许使用_from地址的余额)。

ini 复制代码
    function transferFrom(
        address _from,
        address _to,
        uint256 _value
    ) public returns (bool success) {
        require(_value <= allowance[_from][msg.sender]); // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

approve:授权他人花费代币

scss 复制代码
    function approve(
        address _spender,
        uint256 _value
    ) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

授权并且通知功能

java 复制代码
function approveAndCall(address _spender, uint256 _value, bytes memory _extraData) public returns (bool success) {
    tokenRecipient spender = tokenRecipient(_spender);
    if (approve(_spender, _value)) {
        spender.receiveApproval(msg.sender, _value, address(this), _extraData);
        return true;
    }
}

先调用approve函数,然后调用目标合约的 receiveApproval 方法,在授权的同时,告诉目标合约已经授权给你这些额度。

参数细节:

less 复制代码
address _spender   // 被授权的合约地址
uint256 _value     // 授权额度(最大能花多少代币)
bytes memory _extraData  // 额外数据,可以传给对方合约

receiveApproval参数细节:

kotlin 复制代码
spender.receiveApproval(
    msg.sender,    // 谁授权的
    _value,        // 授权了多少
    address(this), // 这个代币合约的地址
    _extraData     // 附带的额外数据
);

代币销毁

php 复制代码
function burn(uint256 _value) public returns (bool success)
function burnFrom(address _from, uint256 _value) public returns (bool success)

burn:自己销毁代币。

burnFrom:在有授权的情况下,帮别人销毁代币。

这会减少 totalSupply,有一定通缩效果。

代码

scss 复制代码
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface tokenRecipient {
    function receiveApproval(
        address _from,
        uint256 _value,
        address _token,
        bytes calldata _extraData
    ) external;
}

contract ManualToken {
    // Public variables of the token
    string public name;
    string public symbol;
    uint8 public decimals = 18;
    // 18 decimals is the strongly suggested default, avoid changing it
    uint256 public totalSupply;

    // This creates an array with all balances
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    // This generates a public event on the blockchain that will notify clients
    event Transfer(address indexed from, address indexed to, uint256 value);

    // This generates a public event on the blockchain that will notify clients
    event Approval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value
    );

    // This notifies clients about the amount burnt
    event Burn(address indexed from, uint256 value);

    /**
     * Constructor function
     *
     * Initializes contract with initial supply tokens to the creator of the contract
     */
    constructor(
        uint256 initialSupply,
        string memory tokenName,
        string memory tokenSymbol
    ) {
        totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount
        balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens
        name = tokenName; // Set the name for display purposes
        symbol = tokenSymbol; // Set the symbol for display purposes
    }

    /**
     * Internal transfer, only can be called by this contract
     */
    function _transfer(address _from, address _to, uint256 _value) internal {
        // Prevent transfer to 0x0 address. Use burn() instead
        require(_to != address(0x0));
        // Check if the sender has enough
        require(balanceOf[_from] >= _value);
        // Check for overflows
        require(balanceOf[_to] + _value >= balanceOf[_to]);
        // Save this for an assertion in the future
        uint256 previousBalances = balanceOf[_from] + balanceOf[_to];
        // Subtract from the sender
        balanceOf[_from] -= _value;
        // Add the same to the recipient
        balanceOf[_to] += _value;
        emit Transfer(_from, _to, _value);
        // Asserts are used to use static analysis to find bugs in your code. They should never fail
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

    /**
     * Transfer tokens
     *
     * Send `_value` tokens to `_to` from your account
     *
     * @param _to The address of the recipient
     * @param _value the amount to send
     */
    function transfer(
        address _to,
        uint256 _value
    ) public returns (bool success) {
        _transfer(msg.sender, _to, _value);
        return true;
    }

    /**
     * Transfer tokens from other address
     *
     * Send `_value` tokens to `_to` on behalf of `_from`
     *
     * @param _from The address of the sender
     * @param _to The address of the recipient
     * @param _value the amount to send
     */
    function transferFrom(
        address _from,
        address _to,
        uint256 _value
    ) public returns (bool success) {
        require(_value <= allowance[_from][msg.sender]); // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

    /**
     * Set allowance for other address
     *
     * Allows `_spender` to spend no more than `_value` tokens on your behalf
     *
     * @param _spender The address authorized to spend
     * @param _value the max amount they can spend
     */
    function approve(
        address _spender,
        uint256 _value
    ) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    /**
     * Set allowance for other address and notify
     *
     * Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it
     *
     * @param _spender The address authorized to spend
     * @param _value the max amount they can spend
     * @param _extraData some extra information to send to the approved contract
     */
    function approveAndCall(
        address _spender,
        uint256 _value,
        bytes memory _extraData
    ) public returns (bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(
                msg.sender,
                _value,
                address(this),
                _extraData
            );
            return true;
        }
    }

    /**
     * Destroy tokens
     *
     * Remove `_value` tokens from the system irreversibly
     *
     * @param _value the amount of money to burn
     */
    function burn(uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
        balanceOf[msg.sender] -= _value; // Subtract from the sender
        totalSupply -= _value; // Updates totalSupply
        emit Burn(msg.sender, _value);
        return true;
    }

    /**
     * Destroy tokens from other account
     *
     * Remove `_value` tokens from the system irreversibly on behalf of `_from`.
     *
     * @param _from the address of the sender
     * @param _value the amount of money to burn
     */
    function burnFrom(
        address _from,
        uint256 _value
    ) public returns (bool success) {
        require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
        require(_value <= allowance[_from][msg.sender]); // Check allowance
        balanceOf[_from] -= _value; // Subtract from the targeted balance
        allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
        totalSupply -= _value; // Update totalSupply
        emit Burn(_from, _value);
        return true;
    }
}
相关推荐
苏三说技术5 小时前
消息积压了100万,除了加机器,还能干什么?
后端
CN_山居6 小时前
Ubuntu使用Google Authenticator(MFA)
后端
小猪乔治爱打球6 小时前
[Golang 修仙之路] 场景题:红包系统设计
后端·面试
程序猿二饭6 小时前
SpringBoot 实现支持多个微信小程序的登录
后端
AlpsMonaco6 小时前
kubernetes(k8s)集群迁移更新
后端
华仔啊6 小时前
刚学 Java 就被内存溢出劝退?这 10 个集合内存管理技巧救了我!
java·后端
武子康6 小时前
大数据-90 Spark RDD容错机制:Checkpoint原理、场景与最佳实践 容错机制详解
大数据·后端·spark
花花无缺6 小时前
python自动化-pytest-标记
后端·python
Villiam_AY6 小时前
使用 chromedp 高效爬取 Bing 搜索结果
后端·爬虫·golang