From 68a6eaf3d2d22227f5d5519a52f48c6e974aa044 Mon Sep 17 00:00:00 2001 From: Claudia Date: Thu, 1 Jun 2023 01:55:55 +0200 Subject: [PATCH 1/7] add unsafe memory access function and refactor erc1155 to use it --- contracts/token/ERC1155/ERC1155.sol | 15 ++++++++++----- contracts/utils/Arrays.sol | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index a2d7404bff8..bc0a7862ea0 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"; /** * @dev Implementation of the basic standard multi-token. @@ -17,6 +18,9 @@ import "../../utils/introspection/ERC165.sol"; * _Available since v3.1._ */ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { + using Arrays for uint256[]; + using Arrays for address[]; + // Mapping from token ID to account balances mapping(uint256 => mapping(address => uint256)) private _balances; @@ -84,7 +88,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { 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; @@ -160,8 +164,8 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { 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]; @@ -177,8 +181,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { } 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 383a0820aaf..3f98ea72e09 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -102,4 +102,18 @@ library Arrays { } return slot.getUint256Slot(); } + + function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) { + /// @solidity memory-safe-assembly + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } + + function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) { + /// @solidity memory-safe-assembly + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } } From 52c5cd638ed38a6a602e8e7763220da8bcf80836 Mon Sep 17 00:00:00 2001 From: Claudia Date: Fri, 2 Jun 2023 10:16:44 +0200 Subject: [PATCH 2/7] add changeset --- .changeset/hot-coins-judge.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hot-coins-judge.md diff --git a/.changeset/hot-coins-judge.md b/.changeset/hot-coins-judge.md new file mode 100644 index 00000000000..ea17433fd99 --- /dev/null +++ b/.changeset/hot-coins-judge.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +Array optimization in ERC1155 From 02d57d13ede96ff447063ab44ffb9fcaf1a8b0c5 Mon Sep 17 00:00:00 2001 From: Claudia Date: Fri, 2 Jun 2023 10:22:14 +0200 Subject: [PATCH 3/7] fix extra lines --- contracts/token/ERC1155/ERC1155.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index bc0a7862ea0..0bded6612f8 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -183,7 +183,6 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { if (ids.length == 1) { 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); From 686e4f9d1a756e2d613ff1384edff0423afcee43 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 9 Jun 2023 11:20:59 +0200 Subject: [PATCH 4/7] Apply suggestions from code review --- contracts/utils/Arrays.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 3f98ea72e09..1dc9d9dd570 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -104,14 +104,12 @@ library Arrays { } function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) { - /// @solidity memory-safe-assembly assembly { res := mload(add(add(arr, 0x20), mul(pos, 0x20))) } } function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) { - /// @solidity memory-safe-assembly assembly { res := mload(add(add(arr, 0x20), mul(pos, 0x20))) } From 78f93a8c594282bc80ef3f970fe717413fcd8cb9 Mon Sep 17 00:00:00 2001 From: Claudia Barcelo Date: Fri, 9 Jun 2023 16:46:03 +0200 Subject: [PATCH 5/7] Update .changeset/hot-coins-judge.md Co-authored-by: Hadrien Croubois --- .changeset/hot-coins-judge.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/hot-coins-judge.md b/.changeset/hot-coins-judge.md index ea17433fd99..5740e43a99e 100644 --- a/.changeset/hot-coins-judge.md +++ b/.changeset/hot-coins-judge.md @@ -2,4 +2,5 @@ 'openzeppelin-solidity': patch --- -Array optimization in ERC1155 +`Arrays`: Add `unsafeMemoryAccess` helpers to read from a memory array without checking the length. +`ERC1155`: optimize memory accesses From da36cad1b416d5d7410cb46bd7d4818e2d398217 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 14 Jun 2023 23:10:29 -0300 Subject: [PATCH 6/7] add docs --- contracts/utils/Arrays.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 1dc9d9dd570..f4ef45645a8 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -103,12 +103,22 @@ 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))) From f25df971e33c25ed6ec7a9b3fbbc710af572e17f Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Wed, 14 Jun 2023 23:14:21 -0300 Subject: [PATCH 7/7] update changesets update changeset --- .changeset/hot-coins-judge.md | 3 +-- .changeset/tough-drinks-hammer.md | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/tough-drinks-hammer.md diff --git a/.changeset/hot-coins-judge.md b/.changeset/hot-coins-judge.md index 5740e43a99e..e544af46723 100644 --- a/.changeset/hot-coins-judge.md +++ b/.changeset/hot-coins-judge.md @@ -1,6 +1,5 @@ --- -'openzeppelin-solidity': patch +'openzeppelin-solidity': minor --- `Arrays`: Add `unsafeMemoryAccess` helpers to read from a memory array without checking the length. -`ERC1155`: optimize memory accesses diff --git a/.changeset/tough-drinks-hammer.md b/.changeset/tough-drinks-hammer.md new file mode 100644 index 00000000000..51b3836e456 --- /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.