Skip to content

Commit fe5752f

Browse files
Amxxfrangio
authored andcommitted
Use unchecked for ERC721 balance updates (OpenZeppelin#3524)
Co-authored-by: Francisco <frangio.1@gmail.com>
1 parent 1cf4db9 commit fe5752f

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* `ERC721`: optimize transfers by making approval clearing implicit instead of emitting an event. ([#3481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3481))
1313
* `ERC721`: optimize burn by making approval clearing implicit instead of emitting an event. ([#3538](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3538))
1414
* `ERC721`: Fix balance accounting when a custom `_beforeTokenTransfer` hook results in a transfer of the token under consideration. ([#3611](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3611))
15+
* `ERC721`: use unchecked arithmetic for balance updates. ([#3524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3524))
1516
* `ReentrancyGuard`: Reduce code size impact of the modifier by using internal functions. ([#3515](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3515))
1617
* `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565))
1718
* `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605))

contracts/token/ERC721/ERC721.sol

+23-5
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,14 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
285285
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
286286
require(!_exists(tokenId), "ERC721: token already minted");
287287

288-
_balances[to] += 1;
288+
unchecked {
289+
// Will not overflow unless all 2**256 token ids are minted to the same owner.
290+
// Given that tokens are minted one by one, it is impossible in practice that
291+
// this ever happens. Might change if we allow batch minting.
292+
// The ERC fails to describe this case.
293+
_balances[to] += 1;
294+
}
295+
289296
_owners[tokenId] = to;
290297

291298
emit Transfer(address(0), to, tokenId);
@@ -309,13 +316,17 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
309316

310317
_beforeTokenTransfer(owner, address(0), tokenId);
311318

312-
// Update ownership in case tokenId was transfered by `_beforeTokenTransfer` hook
319+
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
313320
owner = ERC721.ownerOf(tokenId);
314321

315322
// Clear approvals
316323
delete _tokenApprovals[tokenId];
317324

318-
_balances[owner] -= 1;
325+
unchecked {
326+
// Cannot overflow, as that would require more tokens to be burned/transferred
327+
// out than the owner initialy received through minting and transferring in.
328+
_balances[owner] -= 1;
329+
}
319330
delete _owners[tokenId];
320331

321332
emit Transfer(owner, address(0), tokenId);
@@ -350,8 +361,15 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
350361
// Clear approvals from the previous owner
351362
delete _tokenApprovals[tokenId];
352363

353-
_balances[from] -= 1;
354-
_balances[to] += 1;
364+
unchecked {
365+
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
366+
// `from`'s balance is the number of token held, which is at least one before the current
367+
// transfer.
368+
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
369+
// all 2**256 token ids to be minted, which in practice is impossible.
370+
_balances[from] -= 1;
371+
_balances[to] += 1;
372+
}
355373
_owners[tokenId] = to;
356374

357375
emit Transfer(from, to, tokenId);

0 commit comments

Comments
 (0)