@@ -155,9 +155,16 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
155
155
if (to == address (0 )) {
156
156
revert ERC721InvalidReceiver (address (0 ));
157
157
}
158
- address owner = _update (to, tokenId, _constraintApprovedOrOwner);
159
- if (owner != from) {
160
- revert ERC721IncorrectOwner (from, tokenId, owner);
158
+
159
+ _checkApproved (from, _msgSender (), tokenId);
160
+ address previousOwner = _update (to, tokenId);
161
+
162
+ if (previousOwner != from) {
163
+ if (previousOwner == address (0 )) {
164
+ revert ERC721NonexistentToken (tokenId);
165
+ } else {
166
+ revert ERC721IncorrectOwner (from, tokenId, previousOwner);
167
+ }
161
168
}
162
169
}
163
170
@@ -223,17 +230,42 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
223
230
}
224
231
225
232
/**
226
- * @dev Returns whether `spender` is allowed to manage `tokenId`.
233
+ * @dev Returns whether `spender` is allowed to manage `tokenId` owned by `owner` .
227
234
*
228
235
* Requirements:
229
236
*
230
237
* - `tokenId` must exist.
231
238
*/
232
- function _isApprovedOrOwner (address spender , uint256 tokenId ) internal view virtual returns (bool ) {
233
- address owner = ownerOf (tokenId);
239
+ function _isApprovedOrOwner (address owner , address spender , uint256 tokenId ) internal view virtual returns (bool ) {
234
240
return (spender == owner || isApprovedForAll (owner, spender) || getApproved (tokenId) == spender);
235
241
}
236
242
243
+ /**
244
+ * @dev Alternative version of {_isApprovedOrOwner} that will fetch the owner from storage.
245
+ * This is not virtual, overrides should be performed on the other version of {_isApprovedOrOwner}
246
+ */
247
+ function _isApprovedOrOwner (address spender , uint256 tokenId ) internal view returns (bool ) {
248
+ return _isApprovedOrOwner (_ownerOf (tokenId), spender, tokenId);
249
+ }
250
+
251
+ /**
252
+ * @dev Check that spender is approved or owner, and revert if that is not the case.
253
+ *
254
+ * NOTE: If the approval is correct, the owner is not checked. If the `owner` parameter is not directly comming
255
+ * from a call to `_ownerOf`, it should be checked. {_update} returns the previousOwner, which should match the
256
+ * `owner` for this function. See {transferFrom}.
257
+ */
258
+ function _checkApproved (address owner , address spender , uint256 tokenId ) internal view virtual {
259
+ if (! _isApprovedOrOwner (owner, spender, tokenId)) {
260
+ address actualOwner = _ownerOf (tokenId);
261
+ if (owner == actualOwner) {
262
+ revert ERC721InsufficientApproval (spender, tokenId);
263
+ } else {
264
+ revert ERC721IncorrectOwner (owner, tokenId, actualOwner);
265
+ }
266
+ }
267
+ }
268
+
237
269
/**
238
270
* @dev Safely mints `tokenId` and transfers it to `to`.
239
271
*
@@ -270,15 +302,9 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
270
302
*
271
303
* Emits a {Transfer} event.
272
304
*/
273
- function _update (
274
- address to ,
275
- uint256 tokenId ,
276
- function (address , address , uint256 ) view constraints
277
- ) internal virtual returns (address ) {
305
+ function _update (address to , uint256 tokenId ) internal virtual returns (address previousOwner ) {
278
306
address from = _ownerOf (tokenId);
279
307
280
- constraints (from, to, tokenId);
281
-
282
308
if (from != address (0 )) {
283
309
delete _tokenApprovals[tokenId];
284
310
unchecked {
@@ -315,7 +341,10 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
315
341
if (to == address (0 )) {
316
342
revert ERC721InvalidReceiver (address (0 ));
317
343
}
318
- _update (to, tokenId, _constraintNotMinted);
344
+ address previousOwner = _update (to, tokenId);
345
+ if (previousOwner != address (0 )) {
346
+ revert ERC721InvalidSender (address (0 ));
347
+ }
319
348
}
320
349
321
350
/**
@@ -330,7 +359,10 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
330
359
* Emits a {Transfer} event.
331
360
*/
332
361
function _burn (uint256 tokenId ) internal {
333
- _update (address (0 ), tokenId, _constraintMinted);
362
+ address previousOwner = _update (address (0 ), tokenId);
363
+ if (previousOwner == address (0 )) {
364
+ revert ERC721NonexistentToken (tokenId);
365
+ }
334
366
}
335
367
336
368
/**
@@ -348,9 +380,9 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
348
380
if (to == address (0 )) {
349
381
revert ERC721InvalidReceiver (address (0 ));
350
382
}
351
- address owner = _update (to, tokenId, _constraintMinted );
352
- if (owner != from) {
353
- revert ERC721IncorrectOwner (from, tokenId, owner );
383
+ address previousOwner = _update (to, tokenId);
384
+ if (previousOwner != from) {
385
+ revert ERC721IncorrectOwner (from, tokenId, previousOwner );
354
386
}
355
387
}
356
388
@@ -386,34 +418,6 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
386
418
}
387
419
}
388
420
389
- /**
390
- * @dev Constraint: revert if token is already minted
391
- */
392
- function _constraintNotMinted (address from , address , uint256 ) internal pure {
393
- if (from != address (0 )) {
394
- revert ERC721InvalidSender (address (0 ));
395
- }
396
- }
397
-
398
- /**
399
- * @dev Constraint: revert if token is not yet minted
400
- */
401
- function _constraintMinted (address from , address , uint256 tokenId ) internal pure {
402
- if (from == address (0 )) {
403
- revert ERC721NonexistentToken (tokenId);
404
- }
405
- }
406
-
407
- /**
408
- * @dev Constraint: check that the caller is the current owner, or approved by the current owner
409
- */
410
- function _constraintApprovedOrOwner (address owner , address , uint256 tokenId ) internal view {
411
- address spender = _msgSender ();
412
- if (spender != owner && ! isApprovedForAll (owner, spender) && getApproved (tokenId) != spender) {
413
- revert ERC721InsufficientApproval (_msgSender (), tokenId);
414
- }
415
- }
416
-
417
421
/**
418
422
* @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address.
419
423
* The call is not executed if the target address is not a contract.
0 commit comments