Ethereum in action - Implementation of ERC20 interface in OpenZeppelin

Understanding ERC20.solof decimals()functions in

The purpose of this function is to tell others the order of magnitude relationship between the unit of measure running in the contract and the unit Weiof measure displayed by the programmer . EthersIt is equivalent to our electronic mall, the unit used internally is cents, but the unit used externally is yuan, so I can return 2 as the base difference.

In the Ethereum network, the unit used internally is Wei, and the unit used externally is Ether, so 18 is returned as the base difference.

  • About viewidentifiers

The viewpurpose of which is to control the function, not to change the state. The most typical are the following three situations:

The first one is undoubtedly changing the state variable; the second one can be simply classified as a viewfunction that cannot send events, once an event is sent, the Ethereum log will change, so it doesn’t count view; creating other contracts, Ethereum’s Data will also change.

In short, any content that makes the Ethereum data possible to change cannot viewappear in the function.

  • About virtualidentifiers

virtualIdentity can be inherited by child contracts, overrideidentity overrides parent contract.

returns(uint8)The content returned by the identifier is of type uint8, which uint8identifies an unsigned 8-bit integer, that is, a number that can represent 0~255, which is completely sufficient.

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

About balanceOffunction to get user balance

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

Note that the balance of this specified account is Weiidentified by unit. Here externalis to tell us that this is an external function, but why not public?

publicIndicates that the function or variable is visible to both the outside and the inside; externalindicates that the function or variable is only visible to the outside and not to the inside.

  1. The logic of the transfer in the following functiontransfer
/**
* @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);

Note that only the address in the parameter is used to receive the transfer money, uint256indicating the amount of the transfer, which externalmeans that it is only visible to the outside world.

The above is just the interface of the function, the following is the implementation

/**
* @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;
}

This document has only 2 lines and returns true by default. If there is an error in the function, an exception will be thrown directly, and the corresponding logic will not be executed.

One of the more interesting things about this function is that _msgSender(), in fact, if I think about it according to my ideas, I think it can be used completely msg.sender(). Why do I need to use a function? I think this should be the author's wish to integrate all common things and contextual things into Context.sol;it, so that unified adjustments can be made.

    /**
     * @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);
    }

The above internalmeans that the function is visible internally and not externally, and there is nothing more to say about the above.

This address(0)means that the sender and receiver cannot be 0 addresses. The display conversion involved here is to convert numbers into addresses. This is to prevent wrong transfers caused by missing addresses.

_beforeTokenTransferIndicates what needs to be done before the transfer. Of course, an exception can be thrown directly to interrupt the transfer function, which is equivalent to using exceptions for all non-mainline logic. In the same way, the one at the bottom _afterTokenTransferindicates what to do after the transfer is successful.

_balancesIt is a mapping type. Its definition is mapping(address => uint256) private _balances;very clear, internally visible, and the balance can be obtained according to the address. So, the following two sentences are easy to understand:

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

Now the following thing is very interesting. First of all, the author requireconfirms that the amount sent must not be greater than the amount transferred. The case says that the transfer can be successful. Why do you need to add uncheckedthis area?

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

Here I think it is more of a psychological comfort, because there will be no overflow of the certificate.

If an element overflows or overflows without adding unchecked, an exception is thrown. Plus unchecked, it will let the value loop, for example

0-1=>2^256-1

internalPassing references can be done efficiently inside functions memorybecause the memory is not emptied. Therefore, it is completely possible to use recursion to process the contents of the array in the smart contract, but it must be noted that the stack slot occupied by recursion cannot be larger than 1024, because each recursive call uses at least one stack slot, and EVMLimit the number of stack slots to 1024.

About the allowance function to get the authorization amount

I think it can be understood as the bank's credit line. There is one here external view, but there is nothing to say above.

    /**
     * @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);

Mainly look at the implementation:

   /**
     * @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;
    }

The first step is to transfer money, and the second part checks whether the credit line is enough to transfer money. If it is not enough, report an exception directly. If it is enough, update the credit line.

As you can see, this function is yes virtual, we can inherit and update the function.

About the approve function

The following is ERC20the function of the credit, this function is an external function, there is nothing to say.

    /**
     * @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);

The following is the implementation of this function

    /**
     * @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);
    }

It is also very simple, but there is a problem with this function. Two credits are added. The first credit is large and the second is small. Does that mean there is a problem?

So far, all the functions in ERC20 have been basically implemented, and the functions implemented below are to make up for the entire process of sending and receiving coins.


increaseAllowanceAnd decreaseAllowanceused to increase and decrease the amount, this is to make up approve.

_mintand _burnis used to issue and burn coins to designated accounts to maintain a healthy currency value.

_beforeTokenTransferThese two _afterTokenTransfermethods appear in all transactions involving transfers, which are equivalent to wrapping in Java. You can do some log-related things or security-related things in these places.

Guess you like

Origin blog.csdn.net/u012331525/article/details/122315928