Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/afraid-chicken-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Bytes`: Add `splice(bytes,uint256)` and `splice(bytes,uint256,uint256)`, two "in place" variants of the existing slice functions
29 changes: 29 additions & 0 deletions contracts/utils/Bytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,35 @@ library Bytes {
return result;
}

/**
* @dev Moves the content of `buffer`, from `start` (included) to the end of `buffer` to the start of that buffer.
*
* NOTE: This function modifies the provided buffer in place. If you need to preserve the original buffer, use {slice} instead
*/
function splice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) {
return splice(buffer, start, buffer.length);
}

/**
* @dev Moves the content of `buffer`, from `start` (included) to end (excluded) to the start of that buffer.
*
* NOTE: This function modifies the provided buffer in place. If you need to preserve the original buffer, use {slice} instead
*/
function splice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) {
// sanitize
uint256 length = buffer.length;
end = Math.min(end, length);
start = Math.min(start, end);

// allocate and copy
assembly ("memory-safe") {
mcopy(add(buffer, 0x20), add(add(buffer, 0x20), start), sub(end, start))
mstore(buffer, sub(end, start))
}

return buffer;
}

/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
Expand Down
8 changes: 5 additions & 3 deletions test/utils/Bytes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ describe('Bytes', function () {
});
});

describe('slice', function () {
describe('slice(bytes, uint256)', function () {
describe('slice & splice', function () {
describe('slice(bytes, uint256) & splice(bytes, uint256)', function () {
for (const [descr, start] of Object.entries({
'start = 0': 0,
'start within bound': 10,
Expand All @@ -66,11 +66,12 @@ describe('Bytes', function () {
it(descr, async function () {
const result = ethers.hexlify(lorem.slice(start));
expect(await this.mock.$slice(lorem, start)).to.equal(result);
expect(await this.mock.$splice(lorem, start)).to.equal(result);
});
}
});

describe('slice(bytes, uint256, uint256)', function () {
describe('slice(bytes, uint256, uint256) & splice(bytes, uint256, uint256)', function () {
for (const [descr, [start, end]] of Object.entries({
'start = 0': [0, 42],
'start and end within bound': [17, 42],
Expand All @@ -81,6 +82,7 @@ describe('Bytes', function () {
it(descr, async function () {
const result = ethers.hexlify(lorem.slice(start, end));
expect(await this.mock.$slice(lorem, start, ethers.Typed.uint256(end))).to.equal(result);
expect(await this.mock.$splice(lorem, start, ethers.Typed.uint256(end))).to.equal(result);
});
}
});
Expand Down