/ lib / ERC1155 / ERC1155.sol
ERC1155.sol
  1  // SPDX-License-Identifier: AGPL-3.0-only
  2  pragma solidity >=0.8.0;
  3  
  4  /// @notice Minimalist and gas efficient standard ERC1155 implementation.
  5  /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
  6  abstract contract ERC1155 {
  7      /*//////////////////////////////////////////////////////////////
  8                                   EVENTS
  9      //////////////////////////////////////////////////////////////*/
 10  
 11      event TransferSingle(
 12          address indexed operator,
 13          address indexed from,
 14          address indexed to,
 15          uint256 id,
 16          uint256 amount
 17      );
 18  
 19      event TransferBatch(
 20          address indexed operator,
 21          address indexed from,
 22          address indexed to,
 23          uint256[] ids,
 24          uint256[] amounts
 25      );
 26  
 27      event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
 28  
 29      event URI(string value, uint256 indexed id);
 30  
 31      /*//////////////////////////////////////////////////////////////
 32                               ERC1155 STORAGE
 33      //////////////////////////////////////////////////////////////*/
 34  
 35      mapping(address => mapping(uint256 => uint256)) internal _balanceOf;
 36  
 37      mapping(address => mapping(address => bool)) public isApprovedForAll;
 38  
 39      /*//////////////////////////////////////////////////////////////
 40                               METADATA LOGIC
 41      //////////////////////////////////////////////////////////////*/
 42  
 43      function uri(uint256 id) public view virtual returns (string memory);
 44  
 45      /*//////////////////////////////////////////////////////////////
 46                                ERC1155 LOGIC
 47      //////////////////////////////////////////////////////////////*/
 48  
 49      function setApprovalForAll(address operator, bool approved) public virtual {
 50          isApprovedForAll[msg.sender][operator] = approved;
 51  
 52          emit ApprovalForAll(msg.sender, operator, approved);
 53      }
 54  
 55      function safeTransferFrom(
 56          address from,
 57          address to,
 58          uint256 id,
 59          uint256 amount,
 60          bytes calldata data
 61      ) public virtual {
 62          require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
 63  
 64          _balanceOf[from][id] -= amount;
 65          _balanceOf[to][id] += amount;
 66  
 67          emit TransferSingle(msg.sender, from, to, id, amount);
 68  
 69          require(
 70              to.code.length == 0
 71                  ? to != address(0)
 72                  : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
 73                      ERC1155TokenReceiver.onERC1155Received.selector,
 74              "UNSAFE_RECIPIENT"
 75          );
 76      }
 77  
 78      function safeBatchTransferFrom(
 79          address from,
 80          address to,
 81          uint256[] calldata ids,
 82          uint256[] calldata amounts,
 83          bytes calldata data
 84      ) public virtual {
 85          require(ids.length == amounts.length, "LENGTH_MISMATCH");
 86  
 87          require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
 88  
 89          // Storing these outside the loop saves ~15 gas per iteration.
 90          uint256 id;
 91          uint256 amount;
 92  
 93          for (uint256 i = 0; i < ids.length; ) {
 94              id = ids[i];
 95              amount = amounts[i];
 96  
 97              _balanceOf[from][id] -= amount;
 98              _balanceOf[to][id] += amount;
 99  
100              // An array can't have a total length
101              // larger than the max uint256 value.
102              unchecked {
103                  ++i;
104              }
105          }
106  
107          emit TransferBatch(msg.sender, from, to, ids, amounts);
108  
109          require(
110              to.code.length == 0
111                  ? to != address(0)
112                  : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) ==
113                      ERC1155TokenReceiver.onERC1155BatchReceived.selector,
114              "UNSAFE_RECIPIENT"
115          );
116      }
117  
118      function balanceOf(address owner, uint256 id) public view virtual returns (uint256 balance) {
119          balance = _balanceOf[owner][id];
120      }
121  
122      function balanceOfBatch(address[] calldata owners, uint256[] calldata ids)
123          public
124          view
125          virtual
126          returns (uint256[] memory balances)
127      {
128          require(owners.length == ids.length, "LENGTH_MISMATCH");
129  
130          balances = new uint256[](owners.length);
131  
132          // Unchecked because the only math done is incrementing
133          // the array index counter which cannot possibly overflow.
134          unchecked {
135              for (uint256 i = 0; i < owners.length; ++i) {
136                  balances[i] = _balanceOf[owners[i]][ids[i]];
137              }
138          }
139      }
140  
141      /*//////////////////////////////////////////////////////////////
142                                ERC165 LOGIC
143      //////////////////////////////////////////////////////////////*/
144  
145      function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
146          return
147              interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
148              interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
149              interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
150      }
151  
152      /*//////////////////////////////////////////////////////////////
153                          INTERNAL MINT/BURN LOGIC
154      //////////////////////////////////////////////////////////////*/
155  
156      function _mint(
157          address to,
158          uint256 id,
159          uint256 amount,
160          bytes memory data
161      ) internal virtual {
162          _balanceOf[to][id] += amount;
163  
164          emit TransferSingle(msg.sender, address(0), to, id, amount);
165  
166          require(
167              to.code.length == 0
168                  ? to != address(0)
169                  : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) ==
170                      ERC1155TokenReceiver.onERC1155Received.selector,
171              "UNSAFE_RECIPIENT"
172          );
173      }
174  
175      function _batchMint(
176          address to,
177          uint256[] memory ids,
178          uint256[] memory amounts,
179          bytes memory data
180      ) internal virtual {
181          uint256 idsLength = ids.length; // Saves MLOADs.
182  
183          require(idsLength == amounts.length, "LENGTH_MISMATCH");
184  
185          for (uint256 i = 0; i < idsLength; ) {
186              _balanceOf[to][ids[i]] += amounts[i];
187  
188              // An array can't have a total length
189              // larger than the max uint256 value.
190              unchecked {
191                  ++i;
192              }
193          }
194  
195          emit TransferBatch(msg.sender, address(0), to, ids, amounts);
196  
197          require(
198              to.code.length == 0
199                  ? to != address(0)
200                  : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) ==
201                      ERC1155TokenReceiver.onERC1155BatchReceived.selector,
202              "UNSAFE_RECIPIENT"
203          );
204      }
205  
206      function _batchBurn(
207          address from,
208          uint256[] memory ids,
209          uint256[] memory amounts
210      ) internal virtual {
211          uint256 idsLength = ids.length; // Saves MLOADs.
212  
213          require(idsLength == amounts.length, "LENGTH_MISMATCH");
214  
215          for (uint256 i = 0; i < idsLength; ) {
216              _balanceOf[from][ids[i]] -= amounts[i];
217  
218              // An array can't have a total length
219              // larger than the max uint256 value.
220              unchecked {
221                  ++i;
222              }
223          }
224  
225          emit TransferBatch(msg.sender, from, address(0), ids, amounts);
226      }
227  
228      function _burn(
229          address from,
230          uint256 id,
231          uint256 amount
232      ) internal virtual {
233          _balanceOf[from][id] -= amount;
234  
235          emit TransferSingle(msg.sender, from, address(0), id, amount);
236      }
237  }
238  
239  /// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
240  /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
241  abstract contract ERC1155TokenReceiver {
242      function onERC1155Received(
243          address,
244          address,
245          uint256,
246          uint256,
247          bytes calldata
248      ) external virtual returns (bytes4) {
249          return ERC1155TokenReceiver.onERC1155Received.selector;
250      }
251  
252      function onERC1155BatchReceived(
253          address,
254          address,
255          uint256[] calldata,
256          uint256[] calldata,
257          bytes calldata
258      ) external virtual returns (bytes4) {
259          return ERC1155TokenReceiver.onERC1155BatchReceived.selector;
260      }
261  }