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 }