前幾天已經討論過 ERC721 為何能夠作為一個擁有權,並且透過 IERC721 和 Openzeppelin 的框架,我們可以製作出屬於自己的一枚 NFT。 今天會討論為何會在 ERC721 的出現後,又產生了一個新的代幣協議,它解決了什麼問題,帶來了什麼便利。
這裡簡短的介紹一下 ERC777 協議的重點。ERC777 是一個向下相容 ERC20 的協議,其主要解決了 ERC20 無法在 msg.value
中放入 ERC20 token 的問題(一般只能在 msg.value
中讀取 ETH 的 balance)與合約收發 ERC20 token 的問題,並且也整合了一些節省 gas fee 的方式(例如將 approve()
與 transferFrom()
整合在同筆交易中)。
我在製作 BAKAJOHN NFT project 合約時,發現了 ERC721 有一些無法達成的事情,像是一次大量 mint 或 transfer,無論是哪一種方法在 ERC721 中都需要使用 for 迴圈來達成,像是這樣:
address[] _TO; // 在另一個 function 中 push address 進去
function transferToManyAddress() {
for (uint i = 0; i < _TO.length; i++) {
safeTransferFrom(msg.sender, _TO[i], _tokenId);
_tokenId++;
}
}
function safeMintAmount(uint amount) {
for (uint i = 0; i < _TO.length; i++) {
_safeMint(_TO, _tokenId)
_tokenId++;
}
}
就像是 ERC777 一樣,ERC1155 融合了前面的不同的特質,並修正了一些內容最後成為一個功能性更多的協議。
ERC1155 又被稱做「Multi Token Standard」。簡單來說,ERC1155 融合了 ERC20 與 ERC721 兩個不同類型的 token 的標準,也就是說可以使用這一個標準同時製作出既是獨一無二又是可量產化的 token。
舉個簡單的例子,我們可以鑄造出不同類型的劍:
鑽石劍x10**10 (id = 0)
金劍x10**13 (id = 1)
銀劍x10**18 (id = 2)
銅劍x10**20 (id = 3)
讓這些不同的指向不同的 metadata,各自代表不同的能力值,下面以鑽石劍為例。
{
"name": "dimond sword",
"description": "Made up of the hardest thing in the world",
"image": "DIMONDSWORD.png",
"strength": 1000
"durability": 1000
}
可以看到 ERC1155 可以製造出類似於 ERC20 與 ERC721 的特質:
而 ERC1155 是如何做到管理不同種類的 token 呢?
事實上非常簡單:
mapping(uint256 => mapping(address => uint256)) private _balances;
ERC1155 使用了一個雙層的 mapping 用來每個地址擁有多少顆「每一種類」的 token。
因此在查詢一個地址中編號為 id 的 balance 時就可以直接使用:
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: address zero is not a valid owner");
return _balances[id][account];
}
查詢一系列編號為 id 的 token balance 時則可以使用:
function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
public
view
virtual
override
returns (uint256[] memory)
{
require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
我們可以用 [address_1, address_2], [0, 1]
的方式來查詢地址中的 token 數量。
透過 IERC1155Metadata 的 extension,讓 ERC1155 的 token 能夠指向各自不同的 metadata。而 ERC1155 則是在 constructor()
,也就是在 deploy contract 的時候填入一個 URI。這個 URI 與 ERC721 metadata 不同的地方是,它只利用了一個 metadata,但是會利用其他方式更改。
例如輸入的是 https://token-cdn-domain/{id}.json
,而部屬後程式會自動將 {id}
視為變數並改動它的數值。
但是要記得在取 json 名稱時需要使用 40 位數的 hex 值作為名稱。例如 id=57899
的 token metadata 名稱就需要取成
https://token-cdn-domain/000000000000000000000000000000000000e22b.json
且其中的十六位數表示法要全部使用小寫。
Mint 時,ERC1155 需要加入的只有 id 與 amount,詳細的 function 內容如下。
function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_afterTokenTransfer(operator, address(0), to, ids, amounts, data);
_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
而實際使用這個 function 時,便是傳入兩個 [id_1, id_2],[num_1, num_2]
參數即可以將這些 token mint 到 to
的地址中了。
我們知道在 ERC721 中 transfer 之前都需要做一個 approve (包含 approvalForAll()
) 的動作,這個動作是為了確認你同意將這枚 token 的所有權轉交給其他人。
ERC1155 也設計了一個雙層 mapping 來管理這一件事情。
mapping(address => mapping(address => bool)) private _operatorApprovals;
且在執行 approve 時也只能使用approvalForAll()
了
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC1155: setting approval status for self");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
下面則是批量轉移的 function
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
}
emit TransferBatch(operator, from, to, ids, amounts);
_afterTokenTransfer(operator, from, to, ids, amounts, data);
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
可以看到 ERC1155 在 transfer 時需要輸入不一樣的參數,ids & amounts。其中填入的兩個 array 的編號會對應到相對應的數量。
以上面的寶劍為例,輸入//ids: [0 , 1]
和 // amounts [100, 1000]
就是從 from
中各轉移鑽石劍 100 把和金劍 1000 把到 to
的地址中。
ERC1155 引用了 ERC20 的功能(發行相同的代幣)與 ERC721 的功能(使用 metadata extension) 改變了一些 ERC721 的功能(像是批量 Mint、Transfer 等),創造出了一個新一代的 Token!
這篇只介紹了一部分的 function,但是想要深入了解的人應該仔細看 reference 中第一與第二個連結才可以完全的了解合約應用與實作。
事實上 ERC1155 可以應用在很多事情上,而我的 ERC1155 Verification System 便是將 ERC1155 設計成不同的 ticket 做使用!明日會實作出這個系統所需要的 ERC1155 系統與一些我們可能會用到的程式邏輯。
若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!
歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4