-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added copyMemory function * Added TODOs * Completed MemoryUtils library. * Changed from copyTo to copy interface * Updated NatSpec * Removed getSize() for `string memory`
- Loading branch information
Showing
4 changed files
with
215 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
/** | ||
* @title MemoryUtils | ||
* @notice A library that provides utility functions for memory manipulation in Solidity. | ||
*/ | ||
library MemoryUtils { | ||
/** | ||
* @notice Copies the contents of the source string to the destination string. | ||
* | ||
* @param source_ The source string to copy from. | ||
* @return destination_ The newly allocated string. | ||
*/ | ||
function copy(string memory source_) internal view returns (string memory destination_) { | ||
destination_ = new string(bytes(source_).length); | ||
|
||
unsafeMemoryCopy(getPointer(source_), getPointer(destination_), bytes(source_).length); | ||
} | ||
|
||
/** | ||
* @notice Copies the contents of the source bytes to the destination bytes. | ||
* | ||
* @param source_ The source bytes to copy from. | ||
* @return destination_ The newly allocated bytes. | ||
*/ | ||
function copy(bytes memory source_) internal view returns (bytes memory destination_) { | ||
destination_ = new bytes(source_.length); | ||
|
||
unsafeMemoryCopy(getPointer(source_), getPointer(destination_), source_.length); | ||
} | ||
|
||
/** | ||
* @notice Copies memory from one location to another efficiently via identity precompile. | ||
* @param sourcePointer_ The offset in the memory from which to copy. | ||
* @param destinationPointer_ The offset in the memory where the result will be copied. | ||
* @param size_ The size of the memory to copy. | ||
* | ||
* @dev This function does not account for free memory pointer and should be used with caution. | ||
* | ||
* This signature of calling identity precompile is: | ||
* staticcall(gas(), address(0x04), argsOffset, argsSize, retOffset, retSize) | ||
*/ | ||
function unsafeMemoryCopy( | ||
uint256 sourcePointer_, | ||
uint256 destinationPointer_, | ||
uint256 size_ | ||
) internal view { | ||
assembly { | ||
pop( | ||
staticcall( | ||
gas(), | ||
4, | ||
add(sourcePointer_, 32), | ||
size_, | ||
add(destinationPointer_, 32), | ||
size_ | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* @notice Returns the memory pointer of the given bytes data. | ||
*/ | ||
function getPointer(bytes memory data) internal pure returns (uint256 pointer) { | ||
assembly { | ||
pointer := data | ||
} | ||
} | ||
|
||
/** | ||
* @notice Returns the memory pointer of the given string data. | ||
*/ | ||
function getPointer(string memory data) internal pure returns (uint256 pointer) { | ||
assembly { | ||
pointer := data | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.4; | ||
|
||
import {MemoryUtils} from "../../../libs/utils/MemoryUtils.sol"; | ||
|
||
contract MemoryUtilsMock { | ||
using MemoryUtils for *; | ||
|
||
function testStringMemoryCopy(string memory data_) external view { | ||
string memory someString = new string(bytes(data_).length); | ||
|
||
require( | ||
keccak256(bytes(data_)) != keccak256(bytes(someString)), | ||
"MemoryUtilsMock: testStringMemoryCopy failed. Initial data and someString are equal" | ||
); | ||
|
||
someString = data_.copy(); | ||
|
||
require( | ||
keccak256(bytes(data_)) == keccak256(bytes(someString)), | ||
"MemoryUtilsMock: testStringMemoryCopy failed. Initial data and someString are not equal" | ||
); | ||
} | ||
|
||
function testBytesMemoryCopy(bytes memory data_) external view { | ||
bytes memory someBytes = new bytes(data_.length); | ||
|
||
require( | ||
keccak256(data_) != keccak256(someBytes), | ||
"MemoryUtilsMock: testBytesMemoryCopy failed. Initial data and someBytes are equal" | ||
); | ||
|
||
someBytes = data_.copy(); | ||
|
||
require( | ||
keccak256(data_) == keccak256(someBytes), | ||
"MemoryUtilsMock: testBytesMemoryCopy failed. Initial data and someBytes are not equal" | ||
); | ||
} | ||
|
||
function testUnsafeMemoryCopy(bytes memory data_) external view { | ||
bytes memory someBytes = new bytes(data_.length); | ||
|
||
require( | ||
keccak256(data_) != keccak256(someBytes), | ||
"MemoryUtilsMock: testBigMemory failed. Initial data and someBytes are equal" | ||
); | ||
|
||
MemoryUtils.unsafeMemoryCopy( | ||
MemoryUtils.getPointer(someBytes), | ||
MemoryUtils.getPointer(data_), | ||
someBytes.length | ||
); | ||
|
||
require( | ||
keccak256(data_) == keccak256(someBytes), | ||
"MemoryUtilsMock: testBigMemory failed. Initial data and someBytes are not equal" | ||
); | ||
} | ||
|
||
function testPartialCopy(bytes memory data_) external view { | ||
bytes memory someBytes = new bytes(data_.length / 2); | ||
|
||
require( | ||
keccak256(data_) != keccak256(someBytes), | ||
"MemoryUtilsMock: testPartialCopy failed. Initial data and someBytes are equal" | ||
); | ||
|
||
MemoryUtils.unsafeMemoryCopy( | ||
MemoryUtils.getPointer(someBytes), | ||
MemoryUtils.getPointer(data_), | ||
someBytes.length | ||
); | ||
|
||
for (uint256 i = 0; i < someBytes.length; i++) { | ||
require( | ||
someBytes[i] == data_[i], | ||
"MemoryUtilsMock: testPartialCopy failed. Initial data and someBytes are not equal" | ||
); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { expect } from "chai"; | ||
import { ethers } from "hardhat"; | ||
|
||
import { Reverter } from "@/test/helpers/reverter"; | ||
|
||
import { MemoryUtilsMock } from "@ethers-v6"; | ||
|
||
describe("MemoryUtils", () => { | ||
const reverter = new Reverter(); | ||
|
||
let mock: MemoryUtilsMock; | ||
|
||
before("setup", async () => { | ||
const MemoryUtilsMock = await ethers.getContractFactory("MemoryUtilsMock"); | ||
mock = await MemoryUtilsMock.deploy(); | ||
|
||
await reverter.snapshot(); | ||
}); | ||
|
||
afterEach(reverter.revert); | ||
|
||
describe("copyMemory", () => { | ||
it("should copy arbitrary chunks of memory (bytes)", async () => { | ||
await expect(mock.testBytesMemoryCopy(ethers.randomBytes(20))).to.be.eventually.fulfilled; | ||
}); | ||
|
||
it("should copy arbitrary chunks of memory (string)", async () => { | ||
await expect(mock.testStringMemoryCopy("Hello, world!")).to.be.eventually.fulfilled; | ||
}); | ||
|
||
it("should copy 20 bytes of memory", async () => { | ||
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(20))).to.be.eventually.fulfilled; | ||
}); | ||
|
||
it("should copy 32 bytes of memory", async () => { | ||
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(32))).to.be.eventually.fulfilled; | ||
}); | ||
|
||
it("should copy 1000 bytes of memory", async () => { | ||
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(1000))).to.be.eventually.fulfilled; | ||
}); | ||
|
||
it("should copy 4096 bytes of memory", async () => { | ||
await expect(mock.testUnsafeMemoryCopy(ethers.randomBytes(4096))).to.be.eventually.fulfilled; | ||
}); | ||
|
||
it("should copy partial chunks of memory", async () => { | ||
await expect(mock.testPartialCopy(ethers.randomBytes(15))).to.be.eventually.fulfilled; | ||
}); | ||
}); | ||
}); |