Bläddra i källkod

Optimize array access in ERC1155 (#4300)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Francisco Giordano <fg@frang.io>
Claudia Barcelo 2 år sedan
förälder
incheckning
05ef6924ac

+ 5 - 0
.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.

+ 5 - 0
.changeset/tough-drinks-hammer.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': patch
+---
+
+`ERC1155`: Optimize array accesses by skipping bounds checking when unnecessary.

+ 9 - 5
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);

+ 22 - 0
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)))
+        }
+    }
 }