許多以太坊上的應用都需要用到隨機數,例如 NFT 隨機抽取 tokenId、抽盲盒、gamefi 戰鬥中隨機分勝負等等。但由於以太坊上所有資料都是公開透明(public)且確定性(deterministic)的,它沒法像其他程式語言一樣提供開發者產生隨機數的方法。今天將介紹鏈上(雜湊函數)和鏈下(chainlink 預言機)隨機數產生的兩種方法,並利用它們做一款 tokenId 隨機鑄造的NFT。
我們可以將一些鏈上的全域變數當作種子,利用 keccak256() 雜湊函數來取得偽隨機數。這是因為雜湊函數具有靈敏度和均一性,可以得到「看似」隨機的結果。下面的 getRandomOnchain() 函數利用全域變數 block.timestamp、msg.sender 和 blockhash(block.number-1) 作為種子來取得隨機數:
/**
* 鏈上偽隨機數生成
* 利用keccak256()打包一些鏈上的全域變數/自訂變數
* 返回時轉換成uint256類型
*/
function getRandomOnchain() public view returns(uint256){
// remix運行blockhash會報錯
bytes32 randomBytes = keccak256(abi.encodePacked(block.timestamp, msg.sender, blockhash(block.number-1)));
return uint256(randomBytes);
}
注意:這個方法不安全,因為 block.timestamp、msg.sender 和 blockhash(block.number-1) 這些變數都是公開的,使用者可以預測出用這些種子產生的隨機數,並挑出他們想要的隨機數執行合約,礦工可以操縱 blockhash 和 block.timestamp,使得產生的隨機數符合他的利益。
我們可以在鏈下產生隨機數,然後透過**預言機(Chainlink)**把隨機數上傳到鏈上。Chainlink 提供 VRF(可驗證隨機函數)服務,鏈上開發者可以支付 LINK 代幣來取得隨機數。Chainlink VRF有兩個版本,第二個版本需要官網註冊並預付費,比第一個版本多許多操作,需要花費更多的 gas,但取消訂閱後可以拿回剩餘的 Link,這裡介紹第二個版本 Chainlink VRF V2。
我們將用一個簡單的合約介紹使用 Chainlink VRF的步驟。 RandomNumberConsumer 合約可以向 VRF 請求隨機數,並儲存在狀態變數 randomWords 中。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
contract RandomNumberConsumer is VRFConsumerBaseV2{
//請求隨機數需要呼叫VRFCoordinatorV2Interface介面
VRFCoordinatorV2Interface COORDINATOR;
// 申請後的subId
uint64 subId;
//存放得到的 requestId 和 隨機數
uint256 public requestId;
uint256[] public randomWords;
/**
* 使用chainlink VRF,建構子需要繼承 VRFConsumerBaseV2
* 不同鏈的參數填的不一樣,可參 https://docs.chain.link/vrf/v2/subscription/supported-networks
* 網路: Sepolia測試網
* Chainlink VRF Coordinator 地址: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625
* LINK 代幣地址: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709
* 30 gwei Key Hash: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c
* Minimum Confirmations 最小確認塊數 : 3 (數字大安全性高,一般填12)
* callbackGasLimit gas限制 : 最大 2,500,000
* Maximum Random Values 一次可以得到的隨機數個數 : 最大 500
*/
address vrfCoordinator = 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625;
bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c;
uint16 requestConfirmations = 3;
uint32 callbackGasLimit = 200_000;
uint32 numWords = 3;
constructor(uint64 s_subId) VRFConsumerBaseV2(vrfCoordinator){
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
subId = s_subId;
}
```
/**
* 向VRF合約申請隨機數
*/
function requestRandomWords() external {
requestId = COORDINATOR.requestRandomWords(
keyHash,
subId,
requestConfirmations,
callbackGasLimit,
numWords
);
}
/**
* VRF合約的回傳函數,驗證隨機數有效之後會自動被調用
* 消耗隨機數的邏輯寫在這裡
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory s_randomWords) internal override {
randomWords = s_randomWords;
}
我們將利用鏈上和鏈下隨機數字來做一款tokenId隨機鑄造的NFT。 Random合約繼承ERC721和VRFConsumerBaseV2合約。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/ERC721.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
contract Random is ERC721, VRFConsumerBaseV2{
// NFT相關
uint256 public totalSupply = 100; // NFT總供給
uint256[100] public ids; // 數組,用於計算可供mint的tokenId,請參閱pickRandomUniqueId()函數。
uint256 public mintCount; // 已經mint的數量
// Chainlink VRF相關參數
// VRFCoordinatorV2Interface
VRFCoordinatorV2Interface COORDINATOR; // 呼叫VRFCoordinatorV2Interface介面
/**
* 使用chainlink VRF,建構子需要繼承 VRFConsumerBaseV2
* 不同鏈的參數填的不一樣,可參 https://docs.chain.link/vrf/v2/subscription/supported-networks
* 網路: Sepolia測試網
* Chainlink VRF Coordinator 地址: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625
* LINK 代幣地址: 0x01BE23585060835E02B77ef475b0Cc51aA1e0709
* 30 gwei Key Hash: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c
* Minimum Confirmations 最小確認塊數 : 3 (數字大安全性高,一般填12)
* callbackGasLimit gas限制 : 最大 2,500,000
* Maximum Random Values 一次可以得到的隨機數個數 : 最大 500
*/
address vrfCoordinator = 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625; // VRF 合約地址
bytes32 keyHash = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c; // VRF唯一識別符
uint16 requestConfirmations = 3; // 確認區塊數
uint32 callbackGasLimit = 1_000_000; // VRF手續費
uint32 numWords = 1; // 請求的隨機數個數
uint64 subId; // 申請的Subscription Id
uint256 public requestId; // 申請標識符
// 記錄申請VRF用於mint的使用者地址
mapping(uint256 => address) public requestToSender;
初始化繼承的 VRFConsumerBaseV2 和 ERC721 合約的相關變數。
constructor(uint64 s_subId)
VRFConsumerBaseV2(vrfCoordinator)
ERC721("WTF Random", "WTF"){
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
subId = s_subId;
}
// 輸入uint256數字,回傳一個可以mint的tokenId
function pickRandomUniqueId(uint256 random) private returns (uint256 tokenId) {
//先計算減法,再計算++,注意(a++,++a)區別
uint256 len = totalSupply - mintCount++; // 可mint數量
require(len > 0, "mint close"); // 所有tokenId被mint完了
uint256 randomIndex = random % len; // 取得鏈上隨機數
//隨機數取模,得到tokenId,作為數組下標,同時記錄value為len-1,如果取模得到的值已存在,則tokenId取該數組下標的value
tokenId = ids[randomIndex] != 0 ? ids[randomIndex] : randomIndex; // 取得tokenId
ids[randomIndex] = ids[len - 1] == 0 ? len - 1 : ids[len - 1]; // 更新ids 列表
ids[len - 1] = 0; // 刪除最後一個元素,能返還gas
}
/**
* 鏈上偽隨機數生成
* keccak256(abi.encodePacked()中填上一些鏈上的全域變數/自訂變數
* 返回時轉換成uint256型
*/
function getRandomOnchain() public view returns(uint256){
/*
* 本例鏈上隨機只依賴區塊哈希,呼叫者位址,和區塊時間,
* 想提高隨機性可以再增加一些屬性例如nonce等,但不能根本解決安全問題
*/
bytes32 randomBytes = keccak256(abi.encodePacked(blockhash(block.number-1), msg.sender, block.timestamp));
return uint256(randomBytes);
}
// 利用鏈上偽隨機數鑄造NFT
function mintRandomOnchain() public {
uint256 _tokenId = pickRandomUniqueId(getRandomOnchain());
_mint(msg.sender, _tokenId);
}
/**
* 呼叫VRF取得隨機數,並mintNFT
* 要呼叫requestRandomness()函數獲取,消耗隨機數的邏輯寫在VRF的回呼函數fulfillRandomness()中
* 在呼叫之前,需要在Subscriptions中轉入足夠的Link
*/
function mintRandomVRF() public {
// 呼叫requestRandomness取得隨機數
requestId = COORDINATOR.requestRandomWords(
keyHash,
subId,
requestConfirmations,
callbackGasLimit,
numWords
);
requestToSender[requestId] = msg.sender;
}
/**
* VRF的回傳函數,由VRF Coordinator呼叫
* 消耗隨機數的邏輯寫在本函數
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory s_randomWords) internal override{
address sender = requestToSender[requestId]; // 从requestToSender中获取minter用户地址
uint256 tokenId = pickRandomUniqueId(s_randomWords[0]); // 利用VRF返回的随机数生成tokenId
_mint(sender, tokenId);
}
使用鏈上隨機數高效,但是不安全;而鏈下隨機數生成依賴於第三方提供的預言機服務,比較安全,但是沒那麼簡單經濟。專案方要根據業務場景來選擇適合自己的方案。
ERC1155標準支援一個合約包含多種代幣。並且我們可以發行一個魔改的無聊猿( BAYC1155):它包含 10000 種代幣,且元資料與 BAYC 一致。
不論是 ERC20 或 ERC721 標準,每個合約都對應一個獨立的代幣。假設我們要在以太坊上打造一個類似《魔獸世界》的大型遊戲,這需要我們對每個裝備都部署一個合約。上千種裝備就要部署和管理上千個合約,這非常麻煩。因此,以太坊 EIP1155 提出了一個多代幣標準 ERC1155,允許一個合約包含多個同質化和非同質化代幣。ERC1155 在 GameFi 應用最多,Decentraland、Sandbox 等知名鏈遊都使用它。
簡單來說,ERC1155 與先前介紹的非同質化代幣標準 ERC721 類似:在 ERC721 中,每個代幣都有一個 tokenId 作為唯一標識,每個 tokenId 只對應一個代幣;而在 ERC1155 中,每一種代幣都有一個 id 作為唯一標識,每個 id 對應一種代幣。這樣,代幣種類就可以非同質的在同一個合約裡管理了,而且每種代幣都有一個網址 uri 來儲存它的元數據,類似 ERC721 的 tokenURI。下面是 ERC1155 的元資料介面合約 IERC1155MetadataURI:
/**
* @dev ERC1155的可選介面,加入了uri()函數查詢元數據
*/
interface IERC1155MetadataURI is IERC1155 {
/**
* @dev 回傳第`id`種類代幣的URI
*/
function uri(uint256 id) external view returns (string memory);
那麼要怎麼區分 ERC1155 中的某類代幣是同質化還是非同質化代幣呢?其實很簡單:如果某一 id 對應的代幣總量為 1,那麼它就是非同質化代幣,類似 ERC721;如果某 id 對應的代幣總量大於 1,那麼他就是同質化代幣,因為這些代幣都分享同一個 id,類似 ERC20。
IERC1155 介面合約抽象化了 EIP1155 需要實現的功能,其中包含 4 個事件和 6 個函數。與 ERC721 不同,因為 ERC1155 包含多類代幣,它實現了批量轉帳和批量餘額查詢,一次操作多種代幣:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.sol";
/**
* @dev ERC1155標準的介面合約,實現了EIP1155的功能
* 詳見:https://eips.ethereum.org/EIPS/eip-1155[EIP]
*/
interface IERC1155 is IERC165 {
/**
* @dev 單類代幣轉帳事件
* 當`value`個`id`種類的代幣被`operator`從`from`轉帳到`to`時釋放
*/
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
/**
* @dev 批量代幣轉帳事件
* ids和values為轉帳的代幣種類和數量陣列
*/
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
/**
* @dev 批量授權事件
* 當`account`將所有代幣授權給`operator`時釋放
*/
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
/**
* @dev 當`id`種類的代幣的URI發生變化時釋放,`value`為新的URI
*/
event URI(string value, uint256 indexed id);
/**
* @dev 持倉查詢,回傳`account`擁有的`id`種類的代幣的持倉量
*/
function balanceOf(address account, uint256 id) external view returns (uint256);
/**
* 批量持倉查詢,`accounts`和`ids`陣列的長度要相等
*/
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external
view
returns (uint256[] memory);
/**
* @dev 批量授權,將呼叫者的代幣授權給`operator`地址。
* 釋放{ApprovalForAll}事件
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev 批次授權查詢,如果授權位址`operator`被`account`授權,則傳回`true`
* 見 {setApprovalForAll}函數
*/
function isApprovedForAll(address account, address operator) external view returns (bool);
/**
* @dev 安全轉賬,將`amount`單位`id`種類的代幣從`from`轉帳給`to`
* 釋放{TransferSingle}事件
* 要求:
* - 如果呼叫者不是`from`位址而是授權位址,則需要得到`from`的授權
* - `from`地址必須有足夠的持倉
* - 如果接收方是合約,則需要實作`IERC1155Receiver`的`onERC1155Received`方法,並傳回對應的值
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
/**
* @dev 批量安全轉帳
* 釋放{TransferBatch}事件
* 要求:
* - `ids`和`amounts`長度相等
* - 如果接收方是合約,則需要實作`IERC1155Receiver`的`onERC1155BatchReceived`方法,並傳回對應的值
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}
與 ERC721 標準類似,為了避免代幣被轉入黑洞合約,ERC1155 要求代幣接收合約繼承 IERC1155Receiver 並實現兩個接收函數:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.sol";
/**
* @dev ERC1155接收合約,要接受ERC1155的安全轉賬,就需要實現這個合約
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev 接受ERC1155安全轉帳`safeTransferFrom`
* 需要回傳 0xf23a6e61 或 `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev 接受ERC1155批量安全轉帳`safeBatchTransferFrom`
* 需要回傳 0xbc197c81 或 `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
ERC1155主合約實現了IERC1155介面合約規定的函數,以及單幣/多幣的鑄造和銷毀函數。
ERC1155 主合約包含 4 個狀態變數:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./IERC1155MetadataURI.sol";
import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/Address.sol";
import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/String.sol";
import "https://github.com/AmazingAng/WTF-Solidity/blob/main/34_ERC721/IERC165.sol";
/**
* @dev ERC1155多代幣標準
* 見 https://eips.ethereum.org/EIPS/eip-1155
*/
contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI {
using Address for address; // 使用Address庫,用isContract來判斷地址是否為合約
using Strings for uint256; // 使用String庫
// Token名稱
string public name;
// Token代號
string public symbol;
// 代幣種類id 到帳戶account 到餘額balances 的映射
mapping(uint256 => mapping(address => uint256)) private _balances;
// address 到 授權地址 的批次授權映射
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* 建構子,初始化`name` 和`symbol`, uri_
*/
constructor(string memory name_, string memory symbol_) {
name = name_;
symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
/**
* @dev 持倉查詢 實現IERC1155的balanceOf,返回account地址的id種類代幣持倉量。
*/
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];
}
/**
* @dev 批量持倉查詢
* 要求:
* `accounts` 和 `ids` 陣列長度相等
*/
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;
}
/**
* @dev 批量授權,調用者授權operator使用其所有代幣
* 釋放{ApprovalForAll}事件
* 條件:msg.sender != operator
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
require(msg.sender != operator, "ERC1155: setting approval status for self");
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
/**
* @dev 查詢批量授權
*/
function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
return _operatorApprovals[account][operator];
}
/**
* @dev 安全轉賬,將`amount`單位的`id`種類代幣從`from`轉賬到`to`
* 釋放 {TransferSingle} 事件
* 要求:
* - to 不能是0位址
* - from擁有足夠的持倉量,且呼叫者擁有授權
* - 如果 to 是智能合約,他必須支援 IERC1155Receiver-onERC1155Received
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
address operator = msg.sender;
// 呼叫者是持有者或是被授權
require(
from == operator || isApprovedForAll(from, operator),
"ERC1155: caller is not token owner nor approved"
);
require(to != address(0), "ERC1155: transfer to the zero address");
// from地址有足夠持倉
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
// 更新持倉量
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
// 釋放事件
emit TransferSingle(operator, from, to, id, amount);
// 安全檢查
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
/**
* @dev 批量安全轉賬,將`amounts`數組單位的`ids`數組種類代幣從`from`轉賬到`to`
* 釋放 {TransferSingle} 事件
* 要求:
* - to 不能是0位址
* - from擁有足夠的持倉量,且呼叫者擁有授權
* - 如果 to 是智能合約, 他必須支援 IERC1155Receiver-onERC1155BatchReceived
* - ids和amounts陣列長度相等
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
address operator = msg.sender;
// 呼叫者是持有者或是被授權
require(
from == operator || isApprovedForAll(from, operator),
"ERC1155: caller is not token owner nor approved"
);
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");
// 透過for循環更新持倉
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);
// 安全檢查
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
/**
* @dev 鑄造
* 釋放 {TransferSingle} 事件
*/
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
address operator = msg.sender;
_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);
_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}
/**
* @dev 批量鑄造
* 釋放 {TransferBatch} 事件
*/
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 = msg.sender;
for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
/**
* @dev 銷毀
*/
function _burn(
address from,
uint256 id,
uint256 amount
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = msg.sender;
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);
}
/**
* @dev 批量銷毀
*/
function _burnBatch(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = msg.sender;
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: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
}
emit TransferBatch(operator, from, address(0), ids, amounts);
}
// @dev ERC1155的安全轉帳檢查
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
// @dev ERC1155的批量安全轉帳檢查
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
bytes4 response
) {
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
/**
* @dev 返回ERC1155的id種類代幣的uri,存放metadata,類似ERC721的tokenURI
*/
function uri(uint256 id) public view virtual override returns (string memory) {
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, id.toString())) : "";
}
/**
* 計算{uri}的BaseURI,uri就是把baseURI和tokenId拼接在一起,需要開發重寫
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
}
我們魔改下 ERC721 標準的無聊猿 BAYC,創造一個免費鑄造的 BAYC1155。我們修改_baseURI()函數,讓 BAYC1155 的 uri 和 BAYC 的 tokenURI 一樣。這樣,BAYC1155元資料會與無聊猿的相同:
// SPDX-License-Identifier: MIT
// by 0xAA
pragma solidity ^0.8.21;
import "./ERC1155.sol";
contract BAYC1155 is ERC1155{
uint256 constant MAX_ID = 10000;
// 建構子
constructor() ERC1155("BAYC1155", "BAYC1155"){
}
//BAYC的baseURI為ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
function _baseURI() internal pure override returns (string memory) {
return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
}
// 鑄造函數
function mint(address to, uint256 id, uint256 amount) external {
// id 不能超过10,000
require(id < MAX_ID, "id overflow");
_mint(to, id, amount, "");
}
// 批量鑄造函數
function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts) external {
// id 不能超過10000
for (uint256 i = 0; i < ids.length; i++) {
require(ids[i] < MAX_ID, "id overflow");
}
_mintBatch(to, ids, amounts, "");
}
}