-
Notifications
You must be signed in to change notification settings - Fork 2
/
EIP721.sol
314 lines (275 loc) · 14 KB
/
EIP721.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
pragma solidity ^0.4.24;
import "./EIP721Interface.sol";
import "./EIP721MetadataInterface.sol";
import "./EIP721EnumerableInterface.sol";
import "./EIP721TokenReceiverInterface.sol";
/*
This is a full implementation of all ERC721's features.
Influenced by OpenZeppelin's implementation with some stylistic changes.
https://github.com/OpenZeppelin/zeppelin-solidity/tree/master/contracts/token/ERC721
*/
contract EIP721 is EIP721Interface, EIP721MetadataInterface, EIP721EnumerableInterface, ERC165Interface {
string public name;
string public symbol;
// all tokens
uint256[] internal allTokens;
// mapping of token IDs to its index in all Tokens array.
mapping(uint256 => uint256) internal allTokensIndex;
// Array of tokens owned by a specific owner
mapping(address => uint256[]) internal ownedTokens;
// Mapping from token ID to owner
mapping(uint256 => address) internal ownerOfToken;
// Mapping of the token ID to where it is in the owner's array.
mapping(uint256 => uint256) internal ownedTokensIndex;
// Mapping of a token to a specifically approved owner.
mapping(uint256 => address) internal approvedOwnerOfToken;
// An operator is allowed to manage all assets of another owner.
mapping(address => mapping (address => bool)) internal operators;
mapping(uint256 => string) internal tokenURIs;
bytes4 internal constant ERC721_BASE_INTERFACE_SIGNATURE = 0x80ac58cd;
bytes4 internal constant ERC721_METADATA_INTERFACE_SIGNATURE = 0x5b5e139f;
bytes4 internal constant ERC721_ENUMERABLE_INTERFACE_SIGNATURE = 0x780e9d63;
bytes4 internal constant ONERC721RECEIVED_FUNCTION_SIGNATURE = 0x150b7a02;
/* Modifiers */
modifier tokenExists(uint256 _tokenId) {
require(ownerOfToken[_tokenId] != 0);
_;
}
// checks: is the owner of the token == msg.sender?
// OR has the owner of the token granted msg.sender access to operate?
modifier allowedToOperate(uint256 _tokenId) {
require(checkIfAllowedToOperate(_tokenId));
_;
}
modifier allowedToTransfer(address _from, address _to, uint256 _tokenId) {
require(checkIfAllowedToOperate(_tokenId) || approvedOwnerOfToken[_tokenId] == msg.sender);
require(ownerOfToken[_tokenId] == _from);
require(_to != 0); //not allowed to burn in transfer method
_;
}
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(address _from, address _to, uint256 _tokenId) public payable
tokenExists(_tokenId)
allowedToTransfer(_from, _to, _tokenId) {
//transfer token
settleTransfer(_from, _to, _tokenId);
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) public payable
tokenExists(_tokenId)
allowedToTransfer(_from, _to, _tokenId) {
settleTransfer(_from, _to, _tokenId);
// check if a smart contract
uint256 size;
assembly { size := extcodesize(_to) } // solhint-disable-line no-inline-assembly
if (size > 0) {
// call on onERC721Received.
require(EIP721TokenReceiverInterface(_to).onERC721Received(msg.sender, _from, _tokenId, data) == ONERC721RECEIVED_FUNCTION_SIGNATURE);
}
}
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to ""
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public payable
tokenExists(_tokenId)
allowedToTransfer(_from, _to, _tokenId) {
settleTransfer(_from, _to, _tokenId);
// check if a smart contract
uint256 size;
assembly { size := extcodesize(_to) } // solhint-disable-line no-inline-assembly
if (size > 0) {
// call on onERC721Received.
require(EIP721TokenReceiverInterface(_to).onERC721Received(msg.sender, _from, _tokenId, "") == ONERC721RECEIVED_FUNCTION_SIGNATURE);
}
}
/// @notice Change or reaffirm the approved address for an NFT.
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId) external payable
tokenExists(_tokenId)
allowedToOperate(_tokenId) {
address owner = ownerOfToken[_tokenId];
internalApprove(owner, _approved, _tokenId);
}
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets.
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators.
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved) external {
require(_operator != msg.sender); // can't make oneself an operator
operators[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
/* public View Functions */
/// @notice Count NFTs tracked by this contract
/// @return A count of valid NFTs tracked by this contract, where each one of
/// them has an assigned and queryable owner not equal to the zero address
function totalSupply() public view returns (uint256) {
return allTokens.length;
}
/// @notice Find the owner of an NFT
/// @param _tokenId The identifier for an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @return The address of the owner of the NFT
function ownerOf(uint256 _tokenId) external view
tokenExists(_tokenId) returns (address) {
return ownerOfToken[_tokenId];
}
/// @notice Enumerate valid NFTs
/// @dev Throws if `_index` >= `totalSupply()`.
/// @param _index A counter less than `totalSupply()`
/// @return The token identifier for the `_index`th NFT,
/// (sort order not specified)
function tokenByIndex(uint256 _index) external view returns (uint256) {
require(_index < allTokens.length);
return allTokens[_index];
}
/// @notice Enumerate NFTs assigned to an owner
/// @dev Throws if `_index` >= `balanceOf(_owner)` or if
/// `_owner` is the zero address, representing invalid NFTs.
/// @param _owner An address where we are interested in NFTs owned by them
/// @param _index A counter less than `balanceOf(_owner)`
/// @return The token identifier for the `_index`th NFT assigned to `_owner`,
/// (sort order not specified)
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view
tokenExists(_tokenId) returns (uint256 _tokenId) {
require(_index < ownedTokens[_owner].length);
return ownedTokens[_owner][_index];
}
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param _owner An address for whom to query the balance
/// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner) external view returns (uint256) {
require(_owner != 0);
return ownedTokens[_owner].length;
}
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT
/// @param _tokenId The NFT to find the approved address for
// todo: The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId) external view
tokenExists(_tokenId) returns (address) {
return approvedOwnerOfToken[_tokenId];
}
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
return operators[_owner][_operator];
}
/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
/// 3986. The URI may point to a JSON file that conforms to the "ERC721
/// Metadata JSON Schema".
function tokenURI(uint256 _tokenId) external view returns (string) {
return tokenURIs[_tokenId];
}
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool) {
if (interfaceID == ERC721_BASE_INTERFACE_SIGNATURE ||
interfaceID == ERC721_METADATA_INTERFACE_SIGNATURE ||
interfaceID == ERC721_ENUMERABLE_INTERFACE_SIGNATURE) {
return true;
} else { return false; }
}
/* -- Internal Functions -- */
function checkIfAllowedToOperate(uint256 _tokenId) internal view returns (bool) {
return ownerOfToken[_tokenId] == msg.sender || operators[ownerOfToken[_tokenId]][msg.sender];
}
function internalApprove(address _owner, address _approved, uint256 _tokenId) internal {
require(_approved != _owner); //can't approve to owner to itself
// Note: the following code is equivalent to: require(getApproved(_tokenId) != 0) || _approved != 0);
// However: I found the following easier to read & understand.
if (approvedOwnerOfToken[_tokenId] == 0 && _approved == 0) {
revert(); // add reason for revert?
} else {
approvedOwnerOfToken[_tokenId] = _approved;
emit Approval(_owner, _approved, _tokenId);
}
}
function settleTransfer(address _from, address _to, uint256 _tokenId) internal {
//clear pending approvals if there are any
if (approvedOwnerOfToken[_tokenId] != 0) {
internalApprove(_from, 0, _tokenId);
}
removeToken(_from, _tokenId);
addToken(_to, _tokenId);
emit Transfer(_from, _to, _tokenId);
}
function addToken(address _to, uint256 _tokenId) internal {
// add new token to all tokens
allTokens.push(_tokenId);
// add new token to index of all tokens.
allTokensIndex[_tokenId] = allTokens.length-1;
// set token to be owned by address _to
ownerOfToken[_tokenId] = _to;
// add that token to an array keeping track of tokens owned by that address
ownedTokens[_to].push(_tokenId);
// add newly pushed to index.
ownedTokensIndex[_tokenId] = ownedTokens[_to].length-1;
}
function removeToken(address _from, uint256 _tokenId) internal {
// remove token from allTokens array.
uint256 allIndex = allTokensIndex[_tokenId];
uint256 allTokensLength = allTokens.length;
//1) Put last tokenID into index of tokenID to be removed.
allTokens[allIndex] = allTokens[allTokensLength - 1];
//2) Take last tokenID that has been moved to the removed token & update its new index
allTokensIndex[allTokens[allTokensLength-1]] = allIndex;
//3) delete last item (since it's now a duplicate)
delete allTokens[allTokensLength-1];
//4) reduce length of array
allTokens.length -= 1;
// remove token from owner array.
// get the index of where this token is in the owner's array
uint256 ownerIndex = ownedTokensIndex[_tokenId];
uint256 ownerLength = ownedTokens[_from].length;
/* Remove Token From Index */
//1) Put last tokenID into index of token to be removed.
ownedTokens[_from][ownerIndex] = ownedTokens[_from][ownerLength-1];
//2) Take last item that has been moved to the removed token & update its index
ownedTokensIndex[ownedTokens[_from][ownerLength-1]] = ownerIndex;
//3) delete last item (since it's now a duplicate)
delete ownedTokens[_from][ownerLength-1];
//4) reduce length of array
ownedTokens[_from].length -= 1;
delete ownerOfToken[_tokenId];
}
}