Add Memory utility library (#5189)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
This commit is contained in:
5
.changeset/dull-students-eat.md
Normal file
5
.changeset/dull-students-eat.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'openzeppelin-solidity': minor
|
||||
---
|
||||
|
||||
`Memory`: Add library with utilities to manipulate memory
|
||||
@ -49,6 +49,7 @@ import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol";
|
||||
import {SignedMath} from "../utils/math/SignedMath.sol";
|
||||
import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||
import {Strings} from "../utils/Strings.sol";
|
||||
import {Memory} from "../utils/Memory.sol";
|
||||
import {Time} from "../utils/types/Time.sol";
|
||||
|
||||
contract Dummy1234 {}
|
||||
|
||||
44
contracts/utils/Memory.sol
Normal file
44
contracts/utils/Memory.sol
Normal file
@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @dev Utilities to manipulate memory.
|
||||
*
|
||||
* Memory is a contiguous and dynamic byte array in which Solidity stores non-primitive types.
|
||||
* This library provides functions to manipulate pointers to this dynamic array.
|
||||
*
|
||||
* WARNING: When manipulating memory, make sure to follow the Solidity documentation
|
||||
* guidelines for https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety[Memory Safety].
|
||||
*/
|
||||
library Memory {
|
||||
type Pointer is bytes32;
|
||||
|
||||
/// @dev Returns a `Pointer` to the current free `Pointer`.
|
||||
function getFreeMemoryPointer() internal pure returns (Pointer ptr) {
|
||||
assembly ("memory-safe") {
|
||||
ptr := mload(0x40)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Sets the free `Pointer` to a specific value.
|
||||
*
|
||||
* WARNING: Everything after the pointer may be overwritten.
|
||||
**/
|
||||
function setFreeMemoryPointer(Pointer ptr) internal pure {
|
||||
assembly ("memory-safe") {
|
||||
mstore(0x40, ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev `Pointer` to `bytes32`. Expects a pointer to a properly ABI-encoded `bytes` object.
|
||||
function asBytes32(Pointer ptr) internal pure returns (bytes32) {
|
||||
return Pointer.unwrap(ptr);
|
||||
}
|
||||
|
||||
/// @dev `bytes32` to `Pointer`. Expects a pointer to a properly ABI-encoded `bytes` object.
|
||||
function asPointer(bytes32 value) internal pure returns (Pointer) {
|
||||
return Pointer.wrap(value);
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
|
||||
* {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes].
|
||||
* {Comparators}: A library that contains comparator functions to use with the {Heap} library.
|
||||
* {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers.
|
||||
* {Memory}: A utility library to manipulate memory.
|
||||
* {InteroperableAddress}: Library for formatting and parsing ERC-7930 interoperable addresses.
|
||||
* {Blockhash}: A library for accessing historical block hashes beyond the standard 256 block limit utilizing EIP-2935's historical blockhash functionality.
|
||||
* {Time}: A library that provides helpers for manipulating time-related objects, including a `Delay` type.
|
||||
@ -135,6 +136,8 @@ Ethereum contracts have no native concept of an interface, so applications must
|
||||
|
||||
{{CAIP10}}
|
||||
|
||||
{{Memory}}
|
||||
|
||||
{{InteroperableAddress}}
|
||||
|
||||
{{Blockhash}}
|
||||
|
||||
@ -263,7 +263,7 @@ Some use cases require more powerful data structures than arrays and mappings of
|
||||
- xref:api:utils.adoc#EnumerableSet[`EnumerableSet`]: A https://en.wikipedia.org/wiki/Set_(abstract_data_type)[set] with enumeration capabilities.
|
||||
- xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]: A `mapping` variant with enumeration capabilities.
|
||||
- xref:api:utils.adoc#MerkleTree[`MerkleTree`]: An on-chain https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] with helper functions.
|
||||
- xref:api:utils.adoc#Heap.sol[`Heap`]: A
|
||||
- xref:api:utils.adoc#Heap.sol[`Heap`]: A https://en.wikipedia.org/wiki/Binary_heap[binary heap] to store elements with priority defined by a compartor function.
|
||||
|
||||
The `Enumerable*` structures are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support _enumeration_, which means you can easily query all stored entries both on and off-chain.
|
||||
|
||||
@ -461,6 +461,38 @@ await instance.multicall([
|
||||
]);
|
||||
----
|
||||
|
||||
=== Memory
|
||||
|
||||
The xref:api:utils.adoc#Memory[`Memory`] library provides functions for advanced use cases that require granular memory management. A common use case is to avoid unnecessary memory expansion costs when performing repeated operations that allocate memory in a loop. Consider the following example:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
function processMultipleItems(uint256[] memory items) internal {
|
||||
for (uint256 i = 0; i < items.length; i++) {
|
||||
bytes memory tempData = abi.encode(items[i], block.timestamp);
|
||||
// Process tempData...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Note that each iteration allocates new memory for `tempData`, causing the memory to expand continuously. This can be optimized by resetting the memory pointer between iterations:
|
||||
|
||||
[source,solidity]
|
||||
----
|
||||
function processMultipleItems(uint256[] memory items) internal {
|
||||
Memory.Pointer ptr = Memory.getFreeMemoryPointer(); // Cache pointer
|
||||
for (uint256 i = 0; i < items.length; i++) {
|
||||
bytes memory tempData = abi.encode(items[i], block.timestamp);
|
||||
// Process tempData...
|
||||
Memory.setFreeMemoryPointer(ptr); // Reset pointer for reuse
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This way, memory allocated for `tempData` in each iteration is reused, significantly reducing memory expansion costs when processing many items.
|
||||
|
||||
IMPORTANT: Only use these functions after carefully confirming they're necessary. By default, Solidity handles memory safely. Using this library without understanding memory layout and safety may be dangerous. See the https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_memory.html[memory layout] and https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety[memory safety] documentation for details.
|
||||
|
||||
=== Historical Block Hashes
|
||||
|
||||
xref:api:utils.adoc#Blockhash[`Blockhash`] provides L2 protocol developers with extended access to historical block hashes beyond Ethereum's native 256-block limit. By leveraging https://eips.ethereum.org/EIPS/eip-2935[EIP-2935]'s history storage contract, the library enables access to block hashes up to 8,191 blocks in the past, making it invaluable for L2 fraud proofs and state verification systems.
|
||||
|
||||
21
test/utils/Memory.t.sol
Normal file
21
test/utils/Memory.t.sol
Normal file
@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {Memory} from "@openzeppelin/contracts/utils/Memory.sol";
|
||||
|
||||
contract MemoryTest is Test {
|
||||
using Memory for *;
|
||||
|
||||
// - first 0x80 bytes are reserved (scratch + FMP + zero)
|
||||
uint256 constant START_PTR = 0x80;
|
||||
// - moving the free memory pointer to far causes OOG errors
|
||||
uint256 constant END_PTR = type(uint24).max;
|
||||
|
||||
function testGetsetFreeMemoryPointer(uint256 seed) public pure {
|
||||
bytes32 ptr = bytes32(bound(seed, START_PTR, END_PTR));
|
||||
ptr.asPointer().setFreeMemoryPointer();
|
||||
assertEq(Memory.getFreeMemoryPointer().asBytes32(), ptr);
|
||||
}
|
||||
}
|
||||
39
test/utils/Memory.test.js
Normal file
39
test/utils/Memory.test.js
Normal file
@ -0,0 +1,39 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$Memory');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('Memory', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('free pointer', function () {
|
||||
it('sets free memory pointer', async function () {
|
||||
const ptr = ethers.toBeHex(0xa0, 32);
|
||||
await expect(this.mock.$setFreeMemoryPointer(ptr)).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('gets free memory pointer', async function () {
|
||||
await expect(this.mock.$getFreeMemoryPointer()).to.eventually.equal(
|
||||
ethers.toBeHex(0x80, 32), // Default pointer
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pointer conversions', function () {
|
||||
it('asBytes32', async function () {
|
||||
const ptr = ethers.toBeHex('0x1234', 32);
|
||||
await expect(this.mock.$asBytes32(ptr)).to.eventually.equal(ptr);
|
||||
});
|
||||
|
||||
it('asPointer', async function () {
|
||||
const ptr = ethers.toBeHex('0x1234', 32);
|
||||
await expect(this.mock.$asPointer(ptr)).to.eventually.equal(ptr);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user