-
-
Notifications
You must be signed in to change notification settings - Fork 804
/
ERC721.vy
342 lines (290 loc) · 11.8 KB
/
ERC721.vy
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#pragma version >0.3.10
###########################################################################
## THIS IS EXAMPLE CODE, NOT MEANT TO BE USED IN PRODUCTION! CAVEAT EMPTOR!
###########################################################################
# @dev example implementation of ERC-721 non-fungible token standard.
# @author Ryuya Nakamura (@nrryuya)
# Modified from: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
from ethereum.ercs import IERC165
from ethereum.ercs import IERC721
implements: IERC721
implements: IERC165
# Interface for the contract called by safeTransferFrom()
interface ERC721Receiver:
def onERC721Received(
_operator: address,
_from: address,
_tokenId: uint256,
_data: Bytes[1024]
) -> bytes4: nonpayable
# @dev Mapping from NFT ID to the address that owns it.
idToOwner: HashMap[uint256, address]
# @dev Mapping from NFT ID to approved address.
idToApprovals: HashMap[uint256, address]
# @dev Mapping from owner address to count of his tokens.
ownerToNFTokenCount: HashMap[address, uint256]
# @dev Mapping from owner address to mapping of operator addresses.
ownerToOperators: HashMap[address, HashMap[address, bool]]
# @dev Address of minter, who can mint a token
minter: address
baseURL: String[53]
# @dev Static list of supported ERC165 interface ids
SUPPORTED_INTERFACES: constant(bytes4[2]) = [
# ERC165 interface ID of ERC165
0x01ffc9a7,
# ERC165 interface ID of ERC721
0x80ac58cd,
]
@deploy
def __init__():
"""
@dev Contract constructor.
"""
self.minter = msg.sender
self.baseURL = "https://api.babby.xyz/metadata/"
@view
@external
def supportsInterface(interface_id: bytes4) -> bool:
"""
@dev Interface identification is specified in ERC-165.
@param interface_id Id of the interface
"""
return interface_id in SUPPORTED_INTERFACES
### VIEW FUNCTIONS ###
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev Returns the number of NFTs owned by `_owner`.
Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
@param _owner Address for whom to query the balance.
"""
assert _owner != empty(address)
return self.ownerToNFTokenCount[_owner]
@view
@external
def ownerOf(_tokenId: uint256) -> address:
"""
@dev Returns the address of the owner of the NFT.
Throws if `_tokenId` is not a valid NFT.
@param _tokenId The identifier for an NFT.
"""
owner: address = self.idToOwner[_tokenId]
# Throws if `_tokenId` is not a valid NFT
assert owner != empty(address)
return owner
@view
@external
def getApproved(_tokenId: uint256) -> address:
"""
@dev Get the approved address for a single NFT.
Throws if `_tokenId` is not a valid NFT.
@param _tokenId ID of the NFT to query the approval of.
"""
# Throws if `_tokenId` is not a valid NFT
assert self.idToOwner[_tokenId] != empty(address)
return self.idToApprovals[_tokenId]
@view
@external
def isApprovedForAll(_owner: address, _operator: address) -> bool:
"""
@dev Checks if `_operator` is an approved operator for `_owner`.
@param _owner The address that owns the NFTs.
@param _operator The address that acts on behalf of the owner.
"""
return (self.ownerToOperators[_owner])[_operator]
### TRANSFER FUNCTION HELPERS ###
@view
@internal
def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
"""
@dev Returns whether the given spender can transfer a given token ID
@param spender address of the spender to query
@param tokenId uint256 ID of the token to be transferred
@return bool whether the msg.sender is approved for the given token ID,
is an operator of the owner, or is the owner of the token
"""
owner: address = self.idToOwner[_tokenId]
spenderIsOwner: bool = owner == _spender
spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
@internal
def _addTokenTo(_to: address, _tokenId: uint256):
"""
@dev Add a NFT to a given address
Throws if `_tokenId` is owned by someone.
"""
# Throws if `_tokenId` is owned by someone
assert self.idToOwner[_tokenId] == empty(address)
# Change the owner
self.idToOwner[_tokenId] = _to
# Change count tracking
self.ownerToNFTokenCount[_to] += 1
@internal
def _removeTokenFrom(_from: address, _tokenId: uint256):
"""
@dev Remove a NFT from a given address
Throws if `_from` is not the current owner.
"""
# Throws if `_from` is not the current owner
assert self.idToOwner[_tokenId] == _from
# Change the owner
self.idToOwner[_tokenId] = empty(address)
# Change count tracking
self.ownerToNFTokenCount[_from] -= 1
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev Clear an approval of a given address
Throws if `_owner` is not the current owner.
"""
# Throws if `_owner` is not the current owner
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != empty(address):
# Reset approvals
self.idToApprovals[_tokenId] = empty(address)
@internal
def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
"""
@dev Exeute transfer of a NFT.
Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
address for this NFT. (NOTE: `msg.sender` not allowed in private function so pass `_sender`.)
Throws if `_to` is the zero address.
Throws if `_from` is not the current owner.
Throws if `_tokenId` is not a valid NFT.
"""
# Check requirements
assert self._isApprovedOrOwner(_sender, _tokenId)
# Throws if `_to` is the zero address
assert _to != empty(address)
# Clear approval. Throws if `_from` is not the current owner
self._clearApproval(_from, _tokenId)
# Remove NFT. Throws if `_tokenId` is not a valid NFT
self._removeTokenFrom(_from, _tokenId)
# Add NFT
self._addTokenTo(_to, _tokenId)
# Log the transfer
log IERC721.Transfer(_from, _to, _tokenId)
### TRANSFER FUNCTIONS ###
@external
@payable
def transferFrom(_from: address, _to: address, _tokenId: uint256):
"""
@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.
@notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
they maybe be permanently lost.
@param _from The current owner of the NFT.
@param _to The new owner.
@param _tokenId The NFT to transfer.
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
@external
@payable
def safeTransferFrom(
_from: address,
_to: address,
_tokenId: uint256,
_data: Bytes[1024]=b""
):
"""
@dev Transfers the ownership of an NFT from one address to another address.
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.
If `_to` is a smart contract, 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`.
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
if _to.is_contract: # check if `_to` is a contract address
returnValue: bytes4 = extcall ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
# Throws if transfer destination is a contract which does not implement 'onERC721Received'
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4)
@external
@payable
def approve(_approved: address, _tokenId: uint256):
"""
@dev Set or reaffirm the approved address for an NFT. 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.
Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
@param _approved Address to be approved for the given NFT ID.
@param _tokenId ID of the token to be approved.
"""
owner: address = self.idToOwner[_tokenId]
# Throws if `_tokenId` is not a valid NFT
assert owner != empty(address)
# Throws if `_approved` is the current owner
assert _approved != owner
# Check requirements
senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
assert (senderIsOwner or senderIsApprovedForAll)
# Set the approval
self.idToApprovals[_tokenId] = _approved
log IERC721.Approval(owner, _approved, _tokenId)
@external
def setApprovalForAll(_operator: address, _approved: bool):
"""
@dev Enables or disables approval for a third party ("operator") to manage all of
`msg.sender`'s assets. It also emits the ApprovalForAll event.
Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
@notice This works even if sender doesn't own any tokens at the time.
@param _operator Address to add to the set of authorized operators.
@param _approved True if the operators is approved, false to revoke approval.
"""
# Throws if `_operator` is the `msg.sender`
assert _operator != msg.sender
self.ownerToOperators[msg.sender][_operator] = _approved
log IERC721.ApprovalForAll(msg.sender, _operator, _approved)
### MINT & BURN FUNCTIONS ###
@external
def mint(_to: address, _tokenId: uint256) -> bool:
"""
@dev Function to mint tokens
Throws if `msg.sender` is not the minter.
Throws if `_to` is zero address.
Throws if `_tokenId` is owned by someone.
@param _to The address that will receive the minted tokens.
@param _tokenId The token id to mint.
@return A boolean that indicates if the operation was successful.
"""
# Throws if `msg.sender` is not the minter
assert msg.sender == self.minter
# Throws if `_to` is zero address
assert _to != empty(address)
# Add NFT. Throws if `_tokenId` is owned by someone
self._addTokenTo(_to, _tokenId)
log IERC721.Transfer(empty(address), _to, _tokenId)
return True
@external
def burn(_tokenId: uint256):
"""
@dev Burns a specific ERC721 token.
Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
address for this NFT.
Throws if `_tokenId` is not a valid NFT.
@param _tokenId uint256 id of the ERC721 token to be burned.
"""
# Check requirements
assert self._isApprovedOrOwner(msg.sender, _tokenId)
owner: address = self.idToOwner[_tokenId]
# Throws if `_tokenId` is not a valid NFT
assert owner != empty(address)
self._clearApproval(owner, _tokenId)
self._removeTokenFrom(owner, _tokenId)
log IERC721.Transfer(owner, empty(address), _tokenId)
@view
@external
def tokenURI(tokenId: uint256) -> String[132]:
return concat(self.baseURL, uint2str(tokenId))