Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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/old-memes-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Bytes`: Add `concat` that merges a `bytes[]` array of buffers into a single `bytes` buffer.
29 changes: 29 additions & 0 deletions contracts/utils/Bytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,35 @@
return buffer;
}

/**
* @dev Concatenate an array of bytes into a single bytes object.
*
* For fixed bytes types, we recommand using the solidity built-in `bytes.concat` or (equivalent)

Check failure on line 134 in contracts/utils/Bytes.sol

View workflow job for this annotation

GitHub Actions / codespell

recommand ==> recommend
*`abi.encodePacked`.
*
* NOTE: this could be done in assembly with a single loop that expands starting at the FMP, but that would be
* significantly less readable. It might be work benchmarking the saving of the full-assembly approach.
*/
function concat(bytes[] memory buffers) internal pure returns (bytes memory) {
uint256 length = 0;
for (uint256 i = 0; i < buffers.length; ++i) {
length += buffers[i].length;
}

bytes memory result = new bytes(length);

uint256 offset = 0x20;
for (uint256 i = 0; i < buffers.length; ++i) {
bytes memory input = buffers[i];
assembly ("memory-safe") {
mcopy(add(result, offset), add(input, 0x20), mload(input))
}
offset += input.length;
}

return result;
}

/**
* @dev Returns true if the two byte buffers are equal.
*/
Expand Down
17 changes: 17 additions & 0 deletions test/utils/Bytes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { MAX_UINT128, MAX_UINT64, MAX_UINT32, MAX_UINT16 } = require('../helpers/constants');
const { generators } = require('../helpers/random');

// Helper functions for fixed bytes types
const bytes32 = value => ethers.toBeHex(value, 32);
Expand Down Expand Up @@ -112,6 +113,22 @@ describe('Bytes', function () {
});
});

describe('concat', function () {
it('empty list', async function () {
await expect(this.mock.$concat([])).to.eventually.equal(generators.bytes.zero);
});

it('single item', async function () {
const item = generators.bytes();
await expect(this.mock.$concat([item])).to.eventually.equal(item);
});

it('multiple items', async function () {
const items = Array.from({ length: 17 }, generators.bytes);
await expect(this.mock.$concat(items)).to.eventually.equal(ethers.concat(items));
});
});

describe('clz bytes', function () {
it('empty buffer', async function () {
await expect(this.mock.$clz('0x')).to.eventually.equal(0);
Expand Down
Loading