Skip to content

Commit 7eba10d

Browse files
Amxxernestognw
andauthored
Move ERC721 and ERC1155 receiver checks to dedicate libraries (#4845)
Co-authored-by: Ernesto García <ernestognw@gmail.com>
1 parent 61117c4 commit 7eba10d

File tree

7 files changed

+540
-101
lines changed

7 files changed

+540
-101
lines changed

.changeset/poor-chefs-cheat.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers.

contracts/token/ERC1155/ERC1155.sol

+3-69
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
pragma solidity ^0.8.20;
55

66
import {IERC1155} from "./IERC1155.sol";
7-
import {IERC1155Receiver} from "./IERC1155Receiver.sol";
87
import {IERC1155MetadataURI} from "./extensions/IERC1155MetadataURI.sol";
8+
import {ERC1155Utils} from "./utils/ERC1155Utils.sol";
99
import {Context} from "../../utils/Context.sol";
1010
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
1111
import {Arrays} from "../../utils/Arrays.sol";
@@ -203,9 +203,9 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER
203203
if (ids.length == 1) {
204204
uint256 id = ids.unsafeMemoryAccess(0);
205205
uint256 value = values.unsafeMemoryAccess(0);
206-
_doSafeTransferAcceptanceCheck(operator, from, to, id, value, data);
206+
ERC1155Utils.checkOnERC1155Received(operator, from, to, id, value, data);
207207
} else {
208-
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data);
208+
ERC1155Utils.checkOnERC1155BatchReceived(operator, from, to, ids, values, data);
209209
}
210210
}
211211
}
@@ -374,72 +374,6 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER
374374
emit ApprovalForAll(owner, operator, approved);
375375
}
376376

377-
/**
378-
* @dev Performs an acceptance check by calling {IERC1155-onERC1155Received} on the `to` address
379-
* if it contains code at the moment of execution.
380-
*/
381-
function _doSafeTransferAcceptanceCheck(
382-
address operator,
383-
address from,
384-
address to,
385-
uint256 id,
386-
uint256 value,
387-
bytes memory data
388-
) private {
389-
if (to.code.length > 0) {
390-
try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) {
391-
if (response != IERC1155Receiver.onERC1155Received.selector) {
392-
// Tokens rejected
393-
revert ERC1155InvalidReceiver(to);
394-
}
395-
} catch (bytes memory reason) {
396-
if (reason.length == 0) {
397-
// non-IERC1155Receiver implementer
398-
revert ERC1155InvalidReceiver(to);
399-
} else {
400-
/// @solidity memory-safe-assembly
401-
assembly {
402-
revert(add(32, reason), mload(reason))
403-
}
404-
}
405-
}
406-
}
407-
}
408-
409-
/**
410-
* @dev Performs a batch acceptance check by calling {IERC1155-onERC1155BatchReceived} on the `to` address
411-
* if it contains code at the moment of execution.
412-
*/
413-
function _doSafeBatchTransferAcceptanceCheck(
414-
address operator,
415-
address from,
416-
address to,
417-
uint256[] memory ids,
418-
uint256[] memory values,
419-
bytes memory data
420-
) private {
421-
if (to.code.length > 0) {
422-
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns (
423-
bytes4 response
424-
) {
425-
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
426-
// Tokens rejected
427-
revert ERC1155InvalidReceiver(to);
428-
}
429-
} catch (bytes memory reason) {
430-
if (reason.length == 0) {
431-
// non-IERC1155Receiver implementer
432-
revert ERC1155InvalidReceiver(to);
433-
} else {
434-
/// @solidity memory-safe-assembly
435-
assembly {
436-
revert(add(32, reason), mload(reason))
437-
}
438-
}
439-
}
440-
}
441-
}
442-
443377
/**
444378
* @dev Creates an array in memory with only one value for each of the elements provided.
445379
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC1155Receiver} from "../IERC1155Receiver.sol";
6+
import {IERC1155Errors} from "../../../interfaces/draft-IERC6093.sol";
7+
8+
/**
9+
* @dev Library that provide common ERC-1155 utility functions.
10+
*
11+
* See https://eips.ethereum.org/EIPS/eip-1155[ERC-1155].
12+
*/
13+
library ERC1155Utils {
14+
/**
15+
* @dev Performs an acceptance check for the provided `operator` by calling {IERC1155-onERC1155Received}
16+
* on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`).
17+
*
18+
* The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA).
19+
* Otherwise, the recipient must implement {IERC1155Receiver-onERC1155Received} and return the acceptance magic value to accept
20+
* the transfer.
21+
*/
22+
function checkOnERC1155Received(
23+
address operator,
24+
address from,
25+
address to,
26+
uint256 id,
27+
uint256 value,
28+
bytes memory data
29+
) internal {
30+
if (to.code.length > 0) {
31+
try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) {
32+
if (response != IERC1155Receiver.onERC1155Received.selector) {
33+
// Tokens rejected
34+
revert IERC1155Errors.ERC1155InvalidReceiver(to);
35+
}
36+
} catch (bytes memory reason) {
37+
if (reason.length == 0) {
38+
// non-IERC1155Receiver implementer
39+
revert IERC1155Errors.ERC1155InvalidReceiver(to);
40+
} else {
41+
/// @solidity memory-safe-assembly
42+
assembly {
43+
revert(add(32, reason), mload(reason))
44+
}
45+
}
46+
}
47+
}
48+
}
49+
50+
/**
51+
* @dev Performs a batch acceptance check for the provided `operator` by calling {IERC1155-onERC1155BatchReceived}
52+
* on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`).
53+
*
54+
* The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA).
55+
* Otherwise, the recipient must implement {IERC1155Receiver-onERC1155Received} and return the acceptance magic value to accept
56+
* the transfer.
57+
*/
58+
function checkOnERC1155BatchReceived(
59+
address operator,
60+
address from,
61+
address to,
62+
uint256[] memory ids,
63+
uint256[] memory values,
64+
bytes memory data
65+
) internal {
66+
if (to.code.length > 0) {
67+
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns (
68+
bytes4 response
69+
) {
70+
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
71+
// Tokens rejected
72+
revert IERC1155Errors.ERC1155InvalidReceiver(to);
73+
}
74+
} catch (bytes memory reason) {
75+
if (reason.length == 0) {
76+
// non-IERC1155Receiver implementer
77+
revert IERC1155Errors.ERC1155InvalidReceiver(to);
78+
} else {
79+
/// @solidity memory-safe-assembly
80+
assembly {
81+
revert(add(32, reason), mload(reason))
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}

contracts/token/ERC721/ERC721.sol

+4-32
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
pragma solidity ^0.8.20;
55

66
import {IERC721} from "./IERC721.sol";
7-
import {IERC721Receiver} from "./IERC721Receiver.sol";
87
import {IERC721Metadata} from "./extensions/IERC721Metadata.sol";
8+
import {ERC721Utils} from "./utils/ERC721Utils.sol";
99
import {Context} from "../../utils/Context.sol";
1010
import {Strings} from "../../utils/Strings.sol";
1111
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
@@ -158,7 +158,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
158158
*/
159159
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
160160
transferFrom(from, to, tokenId);
161-
_checkOnERC721Received(from, to, tokenId, data);
161+
ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
162162
}
163163

164164
/**
@@ -311,7 +311,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
311311
*/
312312
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
313313
_mint(to, tokenId);
314-
_checkOnERC721Received(address(0), to, tokenId, data);
314+
ERC721Utils.checkOnERC721Received(_msgSender(), address(0), to, tokenId, data);
315315
}
316316

317317
/**
@@ -384,7 +384,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
384384
*/
385385
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
386386
_transfer(from, to, tokenId);
387-
_checkOnERC721Received(from, to, tokenId, data);
387+
ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
388388
}
389389

390390
/**
@@ -452,32 +452,4 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er
452452
}
453453
return owner;
454454
}
455-
456-
/**
457-
* @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the
458-
* recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract.
459-
*
460-
* @param from address representing the previous owner of the given token ID
461-
* @param to target address that will receive the tokens
462-
* @param tokenId uint256 ID of the token to be transferred
463-
* @param data bytes optional data to send along with the call
464-
*/
465-
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
466-
if (to.code.length > 0) {
467-
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
468-
if (retval != IERC721Receiver.onERC721Received.selector) {
469-
revert ERC721InvalidReceiver(to);
470-
}
471-
} catch (bytes memory reason) {
472-
if (reason.length == 0) {
473-
revert ERC721InvalidReceiver(to);
474-
} else {
475-
/// @solidity memory-safe-assembly
476-
assembly {
477-
revert(add(32, reason), mload(reason))
478-
}
479-
}
480-
}
481-
}
482-
}
483455
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
import {IERC721Receiver} from "../IERC721Receiver.sol";
6+
import {IERC721Errors} from "../../../interfaces/draft-IERC6093.sol";
7+
8+
/**
9+
* @dev Library that provide common ERC-721 utility functions.
10+
*
11+
* See https://eips.ethereum.org/EIPS/eip-721[ERC-721].
12+
*/
13+
library ERC721Utils {
14+
/**
15+
* @dev Performs an acceptance check for the provided `operator` by calling {IERC721-onERC721Received}
16+
* on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`).
17+
*
18+
* The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA).
19+
* Otherwise, the recipient must implement {IERC721Receiver-onERC721Received} and return the acceptance magic value to accept
20+
* the transfer.
21+
*/
22+
function checkOnERC721Received(
23+
address operator,
24+
address from,
25+
address to,
26+
uint256 tokenId,
27+
bytes memory data
28+
) internal {
29+
if (to.code.length > 0) {
30+
try IERC721Receiver(to).onERC721Received(operator, from, tokenId, data) returns (bytes4 retval) {
31+
if (retval != IERC721Receiver.onERC721Received.selector) {
32+
// Token rejected
33+
revert IERC721Errors.ERC721InvalidReceiver(to);
34+
}
35+
} catch (bytes memory reason) {
36+
if (reason.length == 0) {
37+
// non-IERC721Receiver implementer
38+
revert IERC721Errors.ERC721InvalidReceiver(to);
39+
} else {
40+
/// @solidity memory-safe-assembly
41+
assembly {
42+
revert(add(32, reason), mload(reason))
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)