Skip to content

Commit

Permalink
Optimize array access in ERC1155 (#4300)
Browse files Browse the repository at this point in the history
Co-authored-by: Hadrien Croubois <[email protected]>
Co-authored-by: Francisco Giordano <[email protected]>
  • Loading branch information
3 people authored Jun 15, 2023
1 parent 2477534 commit 05ef692
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-coins-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Arrays`: Add `unsafeMemoryAccess` helpers to read from a memory array without checking the length.
5 changes: 5 additions & 0 deletions .changeset/tough-drinks-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': patch
---

`ERC1155`: Optimize array accesses by skipping bounds checking when unnecessary.
14 changes: 9 additions & 5 deletions contracts/token/ERC1155/ERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand All @@ -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);
Expand Down
22 changes: 22 additions & 0 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
}
}

0 comments on commit 05ef692

Please sign in to comment.