以太坊实战-OpenZeppelin中ERC20接口的实现

关于ERC20.sol中的decimals()函数的理解

这个函数的目的就是告诉别人,合约中运行的计量单位Wei和程序员显示的计量单位Ethers之间的数量级关系。相当于我们在电子商城中,内部使用的单位是分,但是外部使用的单位是元,于是我就可以返回2作为进制差异。

在以太坊网络中,内部使用的单位是Wei,外部使用的单位是Ether,所以,返回18作为进制差异。

  • 关于view标识符

其中的view的目的是控制函数,不可以改变状态。最典型的有下面三个情况:

第一个个毋庸置疑,就是改变状态变量;第二个可以简单归类为view函数不能发送事件,一旦发送事件,那么以太坊的日志就会改变,所以也不算view;创建其他合约,以太坊的数据也会改变。

总之,但凡使得以太坊数据可能发生变更的内容,都不可以在view函数中出现。

  • 关于virtual标识符

virtual标识能被子合约继承,override标识重写了父亲合约。

returns(uint8)标识返回的内容是uint8类型,uint8标识无符号的8位整数,也就是能表示0~255的数字,完全足够。

function decimals() public view virtual override returns (uint8) {
	return 18;
}

关于balanceOf函数来获得用户余额

/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

注意这个指定账户的余额,是按照单位Wei进行标识的。这里的external就是告诉我们,这个是外部函数,但是为什么不用public呢?

public表明函数或者变量,对外部和内部都可见;external表明函数或者变量,只对外部可见,内部不可见。

  1. 下面这个函数时关于转账的逻辑transfer
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);

注意,参数中只有address是用来接收转账钱款的,uint256表示转账的数量,external表示的是只有外部可见。

上面仅仅是函数的接口,下面是实现

/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
}

这个文档就2行,默认返回true。如果函数中出错,直接抛出异常,不会执行相应的逻辑。

该函数中有一个比较有趣的东西是_msgSender(),其实,如果按照我的思路来讲的化,我觉得完全可以使用msg.sender(),为什么需要使用一个函数呢?我觉得这应该是作者希望将所有的公共的东西,上下文的东西,全部整合到Context.sol;中,这样可以做统一调整。

    /**
     * @dev Moves `amount` of tokens from `sender` to `recipient`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[sender] = senderBalance - amount;
        }
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);

        _afterTokenTransfer(sender, recipient, amount);
    }

上面的internal表示该函数内部可见,外部不可见,其他没有上面可以多说的。

这里的address(0)表示的是发送者和接收者不可以是0地址,这里涉及的显示转换,将数字转换为地址,这里是防止因为地址没有填写而造成的错误转账。

_beforeTokenTransfer表示的转帐前需要做的事情,当然也可以直接抛出异常来中断转账函数,相当于非主线逻辑全部使用异常来解决。同理,位于最下面的_afterTokenTransfer表示转账成功后要做的事情。

_balances是一个mapping类型,他的定义是mapping(address => uint256) private _balances;,定义非常明确,内部可见,可以根据地址获得余额。于是,下面这两句很容易理解:

uint256 senderBalance = _balances[sender];
...
_balances[recipient] += amount;

现在下面这个东西很有趣,首先,作者用require确定,发送的金额一定不能大于转账的金额,案例说一定可以转成功的,为什么还需要加unchecked这个区域呢?

require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
	_balances[sender] = senderBalance - amount;
}

这里我认为更多的是一种心理上的安慰,因为不会发证溢出的情况。

如果一个元素向上溢出或者向下溢出,不加unchecked,就会抛出异常。加上unchecked,则会在让数值循环,比如说

0-1=>2^256-1

internal在内部函数可以高效的传递memory引用,因为内存并没有并清空掉。因此,完全可以在智能合约中使用递归的方式来处理数组中的内容,但是千万要注意,递归占用的栈插槽不可以大于1024,因为每次递归调用都至少使用一个栈插槽,而EVM限制栈插槽的数量为1024。

关于allowance函数来获得授权额度

下面我觉得可以理解为银行的授信额度,这里头有一个external view,没有上面可说的。

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

主要是看下实现:

   /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for ``sender``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][_msgSender()];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        unchecked {
            _approve(sender, _msgSender(), currentAllowance - amount);
        }

        return true;
    }

首先第一步是转账,第二部才校验授信额度够不够转账,如果不够的话,直接报异常,如果够的话,更新授信额度。

可以看到,这个函数是virtual的,我们可以继承并更新函数。

关于approve函数

下面是ERC20中的关于授信的函数,这个函数是外部函数,没有什么可以说的。

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

下面是这段函数的实现

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

也非常简单,但是这个函数有一个问题,加入两次授信,第一次授信金额大,第二次授信金额小,那么是不是意味着有问题呢?

到目前位置,ERC20中的所有函数已经基本实现,下面实现的函数都是为了弥补整个收发币的流程。


increaseAllowancedecreaseAllowance用来增加和减少额度,这个是为了弥补approve

_mint_burn用来给指定账户进行发币和燃烧,以维持币值健康。

_beforeTokenTransfer_afterTokenTransfer这俩方法出现在涉及转账的所有交易里头,相当于Java中的环绕,可以在这些地方做一些日志相关的东西或者安全相关的东西。

猜你喜欢

转载自blog.csdn.net/u012331525/article/details/122315928
今日推荐