---
# System prepended metadata

title: 'Krypto Camp 第二週課前學習 ERC20, ERC721'

---

# Krypto Camp 第二週課前學習 ERC20, ERC721
上篇概述了ERC20, ERC721 接下來就來看標準的原始碼是如何寫的。

原始碼來至 [Openzeppelin](https://www.openzeppelin.com/) 安全智能合約開發庫是對於以太坊開發人員來說是必讀的，它具有ERC Token標準，安全協議和其他實用程序的社區審核的實現，這些使開發人員能夠專注於[功能與學習](https://https://docs.openzeppelin.com/contracts)。


### ERC20
- Github : [ERC20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
- 文件說明 : [Openzeppelin ERC20](https://docs.openzeppelin.com/contracts/4.x/erc20)
- IERC20
    - 用於存放Interface
    - 讓外部呼叫合約的人知道可以使用哪些方法

``` =solidity
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

//ERC20合約繼承Context, IERC20, IERC20Metadata
contract ERC20 is Context, IERC20, IERC20Metadata {
    //私有變數 哪一個address有多少token
    mapping(address => uint256) private _balances;
    //私有變數 一個address 管理多個address
    //主要用於第三方
    mapping(address => mapping(address => uint256)) private _allowances;
    //這合約總共token數
    uint256 private _totalSupply;
    //合約名稱
    string private _name;
    //合約簡稱
    string private _symbol;

    //建構式 需傳入 名稱(name), 簡稱(symbol)
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    // 拿合約名稱
    function name() public view virtual override returns (string memory) {
        return _name;
    }
    
    // 拿合約簡稱
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }
    
    //token的小數點精度
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    //token的總發量
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    //address擁有多少token
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    //轉多少token給 哪個地址(to)
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        //當下呼叫此函式的地址
        address owner = _msgSender();
        //內部函式 - 真正轉移的函式
        _transfer(owner, to, amount);
        return true;
    }
    
    //通常提供給第三方使用(第三方通常是去中心化交易平台)
    //查詢第三方能使用owner多少token
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    //設定spender能花多少token
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        //當下呼叫此函式的地址
        address owner = _msgSender();
        //內部函式 - 設定spender能花owner多少token
        _approve(owner, spender, amount);
        return true;
    }

    //指定從哪一個地址(from)轉多少token給 另一個地址(to)
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        //當下呼叫此函式的地址
        address spender = _msgSender();
        //內部函式 - 更新第三方能使用token的數量
        _spendAllowance(from, spender, amount);
        //內部函式 - 真正轉移的函式
        _transfer(from, to, amount);
        return true;
    }

    //增加spender可以使用的token
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        //當下呼叫此函式的地址
        address owner = _msgSender();
        //內部函式 - 設定 spender 可以使用owner多少token
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    //減少spender可以使用的token
    //跟 increaseAllowance(增加token)相反
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    //內部函式 - 從哪一個address(from)轉移多少token(amount)給哪一個address(to)
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        //判斷from是不是 0地址
        require(from != address(0), "ERC20: transfer from the zero address");
        //判斷to是不是 0地址
        require(to != address(0), "ERC20: transfer to the zero address");

        //呼叫token轉移前要做的函式
        //繼承可override此函式
        _beforeTokenTransfer(from, to, amount);

        //把from有多少token拿出來
        uint256 fromBalance = _balances[from];
        //判斷是否有足夠的token
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            //把原本的token數(fromBalance)減掉要轉移的數量(amount), 然後在存回去(_balances[from])
            _balances[from] = fromBalance - amount;
        }
        //轉移的地址(_balances[to]) 增加 指定的token數(amount)
        _balances[to] += amount;

        //在區塊鏈上記錄Transfer的LOG訊息
        emit Transfer(from, to, amount);

        //呼叫token轉移完要做的函式
        //繼承可override此函式
        _afterTokenTransfer(from, to, amount);
    }

    //內部函式 - 哪一個address(account)要鑄造多少token(amount)
    function _mint(address account, uint256 amount) internal virtual {
        //判斷想鑄造的地址是不是 0地址
        require(account != address(0), "ERC20: mint to the zero address");

        //呼叫token轉移前要做的函式
        //繼承可override此函式
        _beforeTokenTransfer(address(0), account, amount);

        //總量加上鑄造多少token(amount)
        _totalSupply += amount;
        //地址token數量 加上鑄造多少token(amount)
        _balances[account] += amount;
        //在區塊鏈上記錄Transfer的LOG訊息
        emit Transfer(address(0), account, amount);

        //呼叫token轉移後要做的函式
        //繼承可override此函式
        _afterTokenTransfer(address(0), account, amount);
    }

    //內部函式 - 哪一個address要燃燒多少token
    //跟mint相反
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        //判斷擁有token數量是不是比燃燒數量大
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    //內部函式 - 設定哪一個address(spender)可以使用哪一個address(owner)多少token(amount)
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        //判斷owner地址是不是 0地址
        require(owner != address(0), "ERC20: approve from the zero address");
        //判斷spender地址是不是 0地址
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        //在區塊鏈上記錄Approval的LOG訊息
        emit Approval(owner, spender, amount);
    }

    //內部函式 - 更新哪一個address(spender)可以使用哪一個address(owner)多少token(amount)
    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    //內部函式 - token轉移前的virtual函式
    //繼承可自行override實作
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    //內部函式 - token轉移後的virtual函式
    //繼承可自行override實作
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}
```



### ERC721
- Github : [ERC721](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol)
- 文件說明 : [Openzeppelin ERC721](https://docs.openzeppelin.com/contracts/4.x/erc721)
- IERC721
    - 用於存放Interface
    - 讓外部呼叫合約的人知道可以使用哪些方法
    
>#### URI（Uniform Resource Identifier）統一資源識別符號
> URI 是肯定能找到對應資源的。
> 如：localhost:80/index.html 直接定位到伺服器上的資源，localhost:80/index.html 就是一個 URI。
簡單來說，能直接找到資源的都是 URI。

``` =solidity
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    string private _name;
    string private _symbol;

    //私有變數 記錄 tokenID是哪一個address擁有的
    mapping(uint256 => address) private _owners;

    //私有變數 記錄 哪一個address擁有多少tokenID數量
    mapping(address => uint256) private _balances;

    //私有變數 記錄 tokenID是哪一個address操作
    //通常是給第三方使用(例如opensea)
    mapping(uint256 => address) private _tokenApprovals;

    //私有變數 記錄 擁有者是否有授權給其他人使用
    //通常是給第三方使用(例如opensea)
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }
    
    //查詢owner有多少tokenID
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }

    //查詢 tokenID是哪一個地址擁有
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    //從tokenID去拿token URI
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    //資源實際位置
    //通常是鏈下的
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    //設定授權tokenID給address(to)使用
    //通常是第三方，例如opensea
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }

    //查詢 tokenID有授權使用的地址
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
    }

    //設定授權給第三方使用
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    //查詢owner有沒有授權給operator使用
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        
        _transfer(from, to, tokenId);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public virtual override {
       require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, data);
    }

    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }
    
    //內部函式 - 哪一個address(to)鑄造 tokenID
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        //address(to) token數量加1
        _balances[to] += 1;
        //把to 變更成 tokenID的擁有者
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId);
    }

    //內部函式 - 哪一個address(to)燃燒 tokenID
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // 取消前擁有者的tokenID的授權
        _approve(address(0), tokenId);

        //address(owner) token數量減1
        _balances[owner] -= 1;
        //重制tokenID的owner為 0
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId);
    }

    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // 取消前擁有者的tokenID的授權
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId);
    }

    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}
```

#### 延伸註解
在ERC721中，函式名中有多safe命名, 例如_safeMint, _safeTransfer那跟沒有safe差別在哪?
主要差別在safe函式中會呼叫下列函式，
其主要目的為 : 接受方為合約賬戶時，可以通過重寫方法 onERC721Received進行交易的安全校驗。

如果tokenID的接收者是合約，它會檢查合約是否實現了 onERC721Received 介面。
如果否，它會還原交易。
如果是，接收者合約有一個onERC721Received方法。ERC721 呼叫這個方法，現在執行到接收者合約來做他想做的任何事情。例如 - 質押收到的代幣。

``` =solidity
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }
```

