我們將 Client 的 QRCode 在前端做了驗證後,我想要設計一個方式讓這個票卷不能重複使用,原本的想法是利用 Dynamic NFT 的方法,將其 metadata 指向別的地方以表示這張票卷已使用過了。但是由於 ERC-1155 的特性,每一種 id 對應到的是其中一種票卷而不是每個人擁有的不同 token,因此在這邊我想到的做法是將驗證後的票卷燒毀(burn),並再重新 mint 一張新的 token 到使用者的地址。
在之前 ERC-721 介紹的時候有提到 Transfer()
這個 event,當 mint()
的時候,其中的 _from
是 zero address;反過來當 burn()
時,其中的 _to
會是 zero address。
因此可以來觀察 Openzeppelin 的合約中是怎麼實作 _burn()
這個 funciton 的:
/**
* @dev Destroys `amount` tokens of token type `id` from `from`
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `amount` tokens of token type `id`.
*/
function _burn(
address from,
uint256 id,
uint256 amount
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
emit TransferSingle(operator, from, address(0), id, amount);
_afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
首先傳入的參數是
address: from
: 燒毀 token 的地址。uint256: id
: 燒毀何種 id 的 token。uint256: amount
: 燒毀多少 token。_asSingltonArray
可以複製一個動態的 memory array(size=1)
裡面只會放一個 element=param 這樣做的原因是因為 OZ 中 ERC1155 的 _beforeTransferToken()
參數需要傳入 array,就與 mintBatch() 的方式類似,可參考這個合約。
_berforeTokenTransfer()
可以在 OZ 的 ERC1155/extensions/ERC1155Supply 中找到。
它做的事情就是去完成 mint()
與 burn()
時對於全部 token 改動的事情,這邊我們專注在 burn()
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
if (from == address(0)) {
for (uint256 i = 0; i < ids.length; ++i) {
_totalSupply[ids[i]] += amounts[i];
}
}
if (to == address(0)) {
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 supply = _totalSupply[id];
require(supply >= amount, "ERC1155: burn amount exceeds totalSupply");
unchecked {
_totalSupply[id] = supply - amount;
}
}
}
}
當 to
為 zero address 時,會遍歷整個 ids
,並使用 uncheck{ }
來將總數量減掉 amount。
solidity 0.8 版本後會自行確認溢位(overflow)與下溢(underflow),使用 uncheck 可以讓程式 compile 時不幫你確認,但可以節省 gas fee。
隨後再使用與 _berforeTokenTransfer()
相同的手法,將 address
中擁有的 token 數量減掉amount
。
afterTokenTransfer()
雖然在 OZ 的 ERC1155 中有宣告這個函式,但是沒有在其他合約中看到函式定義的部分,我推測這個函式可能是為了讓合約撰寫者想要在 token 被轉移之後作出某些事情而宣告,應該是可以讓使用者自行 override 並實作的。在這邊因為我們想要的效果就是燒毀就好了,因此不需要其他的實作。
這邊我直接 import OZ 的合約:ERC1155Burnable.sol
import "@openzeppelin/contracts@4.7.3/token/ERC1155/extensions/ERC1155Burnable.sol";
contract {
function ...
burn(address, id, amount);
}
最後因為我們是在前端驗證的,因此當驗證成功的時候驗證者會透過前端呼叫這個函式。其中編號 4 的 token 是觀眾紀念票!
...
uint256 constant private AUDIENCE_SOUVENIR = 4;
...
function verifySuccessed(address user) public onlyOwner {
burn(user, 3, 1);
_mint(user, 4, 1, "");
}
我原本的驗證設計是:「驗證者」需要透過 metamask 登入才能花費 gas 執行 verifySuccessed()
的動作,但是當有幾千人或是幾萬人同時進行這樣的動作時,肯定是太慢了。因此明天會將重點放在如何改善這個流程,讓驗證的速度更快更流暢,但同時也需要維護一些安全性上的考量。
若有文章內有任何錯誤的地方歡迎指點與討論!非常感謝!
歡迎贊助窮困潦倒大學生
0xd8538ea74825080c0c80B9B175f57e91Ff885Cb4