diff --git a/.changeset/hot-coins-judge.md b/.changeset/hot-coins-judge.md new file mode 100644 index 000000000..e544af467 --- /dev/null +++ b/.changeset/hot-coins-judge.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Arrays`: Add `unsafeMemoryAccess` helpers to read from a memory array without checking the length. diff --git a/.changeset/tough-drinks-hammer.md b/.changeset/tough-drinks-hammer.md new file mode 100644 index 000000000..51b3836e4 --- /dev/null +++ b/.changeset/tough-drinks-hammer.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC1155`: Optimize array accesses by skipping bounds checking when unnecessary. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 610868489..5369d7732 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -8,6 +8,7 @@ import "./IERC1155Receiver.sol"; import "./extensions/IERC1155MetadataURI.sol"; import "../../utils/Context.sol"; import "../../utils/introspection/ERC165.sol"; +import "../../utils/Arrays.sol"; import "../../interfaces/draft-IERC6093.sol"; /** @@ -18,6 +19,9 @@ import "../../interfaces/draft-IERC6093.sol"; * _Available since v3.1._ */ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { + using Arrays for uint256[]; + using Arrays for address[]; + // Mapping from token ID to account balances mapping(uint256 => mapping(address => uint256)) private _balances; @@ -87,7 +91,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER uint256[] memory batchBalances = new uint256[](accounts.length); for (uint256 i = 0; i < accounts.length; ++i) { - batchBalances[i] = balanceOf(accounts[i], ids[i]); + batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i)); } return batchBalances; @@ -157,8 +161,8 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER address operator = _msgSender(); for (uint256 i = 0; i < ids.length; ++i) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; + uint256 id = ids.unsafeMemoryAccess(i); + uint256 amount = amounts.unsafeMemoryAccess(i); if (from != address(0)) { uint256 fromBalance = _balances[id][from]; @@ -176,8 +180,8 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER } if (ids.length == 1) { - uint256 id = ids[0]; - uint256 amount = amounts[0]; + uint256 id = ids.unsafeMemoryAccess(0); + uint256 amount = amounts.unsafeMemoryAccess(0); emit TransferSingle(operator, from, to, id, amount); if (to != address(0)) { _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 383a0820a..f4ef45645 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -102,4 +102,26 @@ library Arrays { } return slot.getUint256Slot(); } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) { + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) { + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } }