Skip to content

Commit db62705

Browse files
authored
Add copyMemory function (#99)
* Added copyMemory function * Added TODOs * Completed MemoryUtils library. * Changed from copyTo to copy interface * Updated NatSpec * Removed getSize() for `string memory`
1 parent ef1cd92 commit db62705

File tree

4 files changed

+215
-2
lines changed

4 files changed

+215
-2
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
/**
5+
* @title MemoryUtils
6+
* @notice A library that provides utility functions for memory manipulation in Solidity.
7+
*/
8+
library MemoryUtils {
9+
/**
10+
* @notice Copies the contents of the source string to the destination string.
11+
*
12+
* @param source_ The source string to copy from.
13+
* @return destination_ The newly allocated string.
14+
*/
15+
function copy(string memory source_) internal view returns (string memory destination_) {
16+
destination_ = new string(bytes(source_).length);
17+
18+
unsafeMemoryCopy(getPointer(source_), getPointer(destination_), bytes(source_).length);
19+
}
20+
21+
/**
22+
* @notice Copies the contents of the source bytes to the destination bytes.
23+
*
24+
* @param source_ The source bytes to copy from.
25+
* @return destination_ The newly allocated bytes.
26+
*/
27+
function copy(bytes memory source_) internal view returns (bytes memory destination_) {
28+
destination_ = new bytes(source_.length);
29+
30+
unsafeMemoryCopy(getPointer(source_), getPointer(destination_), source_.length);
31+
}
32+
33+
/**
34+
* @notice Copies memory from one location to another efficiently via identity precompile.
35+
* @param sourcePointer_ The offset in the memory from which to copy.
36+
* @param destinationPointer_ The offset in the memory where the result will be copied.
37+
* @param size_ The size of the memory to copy.
38+
*
39+
* @dev This function does not account for free memory pointer and should be used with caution.
40+
*
41+
* This signature of calling identity precompile is:
42+
* staticcall(gas(), address(0x04), argsOffset, argsSize, retOffset, retSize)
43+
*/
44+
function unsafeMemoryCopy(
45+
uint256 sourcePointer_,
46+
uint256 destinationPointer_,
47+
uint256 size_
48+
) internal view {
49+
assembly {
50+
pop(
51+
staticcall(
52+
gas(),
53+
4,
54+
add(sourcePointer_, 32),
55+
size_,
56+
add(destinationPointer_, 32),
57+
size_
58+
)
59+
)
60+
}
61+
}
62+
63+
/**
64+
* @notice Returns the memory pointer of the given bytes data.
65+
*/
66+
function getPointer(bytes memory data) internal pure returns (uint256 pointer) {
67+
assembly {
68+
pointer := data
69+
}
70+
}
71+
72+
/**
73+
* @notice Returns the memory pointer of the given string data.
74+
*/
75+
function getPointer(string memory data) internal pure returns (uint256 pointer) {
76+
assembly {
77+
pointer := data
78+
}
79+
}
80+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import {MemoryUtils} from "../../../libs/utils/MemoryUtils.sol";
5+
6+
contract MemoryUtilsMock {
7+
using MemoryUtils for *;
8+
9+
function testStringMemoryCopy(string memory data_) external view {
10+
string memory someString = new string(bytes(data_).length);
11+
12+
require(
13+
keccak256(bytes(data_)) != keccak256(bytes(someString)),
14+
"MemoryUtilsMock: testStringMemoryCopy failed. Initial data and someString are equal"
15+
);
16+
17+
someString = data_.copy();
18+
19+
require(
20+
keccak256(bytes(data_)) == keccak256(bytes(someString)),
21+
"MemoryUtilsMock: testStringMemoryCopy failed. Initial data and someString are not equal"
22+
);
23+
}
24+
25+
function testBytesMemoryCopy(bytes memory data_) external view {
26+
bytes memory someBytes = new bytes(data_.length);
27+
28+
require(
29+
keccak256(data_) != keccak256(someBytes),
30+
"MemoryUtilsMock: testBytesMemoryCopy failed. Initial data and someBytes are equal"
31+
);
32+
33+
someBytes = data_.copy();
34+
35+
require(
36+
keccak256(data_) == keccak256(someBytes),
37+
"MemoryUtilsMock: testBytesMemoryCopy failed. Initial data and someBytes are not equal"
38+
);
39+
}
40+
41+
function testUnsafeMemoryCopy(bytes memory data_) external view {
42+
bytes memory someBytes = new bytes(data_.length);
43+
44+
require(
45+
keccak256(data_) != keccak256(someBytes),
46+
"MemoryUtilsMock: testBigMemory failed. Initial data and someBytes are equal"
47+
);
48+
49+
MemoryUtils.unsafeMemoryCopy(
50+
MemoryUtils.getPointer(someBytes),
51+
MemoryUtils.getPointer(data_),
52+
someBytes.length
53+
);
54+
55+
require(
56+
keccak256(data_) == keccak256(someBytes),
57+
"MemoryUtilsMock: testBigMemory failed. Initial data and someBytes are not equal"
58+
);
59+
}
60+
61+
function testPartialCopy(bytes memory data_) external view {
62+
bytes memory someBytes = new bytes(data_.length / 2);
63+
64+
require(
65+
keccak256(data_) != keccak256(someBytes),
66+
"MemoryUtilsMock: testPartialCopy failed. Initial data and someBytes are equal"
67+
);
68+
69+
MemoryUtils.unsafeMemoryCopy(
70+
MemoryUtils.getPointer(someBytes),
71+
MemoryUtils.getPointer(data_),
72+
someBytes.length
73+
);
74+
75+
for (uint256 i = 0; i < someBytes.length; i++) {
76+
require(
77+
someBytes[i] == data_[i],
78+
"MemoryUtilsMock: testPartialCopy failed. Initial data and someBytes are not equal"
79+
);
80+
}
81+
}
82+
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { expect } from "chai";
2+
import { ethers } from "hardhat";
3+
4+
import { Reverter } from "@/test/helpers/reverter";
5+
6+
import { MemoryUtilsMock } from "@ethers-v6";
7+
8+
describe("MemoryUtils", () => {
9+
const reverter = new Reverter();
10+
11+
let mock: MemoryUtilsMock;
12+
13+
before("setup", async () => {
14+
const MemoryUtilsMock = await ethers.getContractFactory("MemoryUtilsMock");
15+
mock = await MemoryUtilsMock.deploy();
16+
17+
await reverter.snapshot();
18+
});
19+
20+
afterEach(reverter.revert);
21+
22+
describe("copyMemory", () => {
23+
it("should copy arbitrary chunks of memory (bytes)", async () => {
24+
await expect(mock.testBytesMemoryCopy(ethers.randomBytes(20))).to.be.eventually.fulfilled;
25+
});
26+
27+
it("should copy arbitrary chunks of memory (string)", async () => {
28+
await expect(mock.testStringMemoryCopy("Hello, world!")).to.be.eventually.fulfilled;
29+
});
30+
31+
it("should copy 20 bytes of memory", async () => {
32+
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(20))).to.be.eventually.fulfilled;
33+
});
34+
35+
it("should copy 32 bytes of memory", async () => {
36+
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(32))).to.be.eventually.fulfilled;
37+
});
38+
39+
it("should copy 1000 bytes of memory", async () => {
40+
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(1000))).to.be.eventually.fulfilled;
41+
});
42+
43+
it("should copy 4096 bytes of memory", async () => {
44+
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(4096))).to.be.eventually.fulfilled;
45+
});
46+
47+
it("should copy partial chunks of memory", async () => {
48+
await expect(mock.testPartialCopy(ethers.randomBytes(15))).to.be.eventually.fulfilled;
49+
});
50+
});
51+
});

0 commit comments

Comments
 (0)