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