# 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;
}
}
```