Here's something I wrote for this purpose.
function MergeBytes(bytes memory a, bytes memory b) public pure returns (bytes memory c) {
// Store the length of the first array
uint alen = a.length;
// Store the length of BOTH arrays
uint totallen = alen + b.length;
// Count the loops required for array a (sets of 32 bytes)
uint loopsa = (a.length + 31) / 32;
// Count the loops required for array b (sets of 32 bytes)
uint loopsb = (b.length + 31) / 32;
assembly {
let m := mload(0x40)
// Load the length of both arrays to the head of the new bytes array
mstore(m, totallen)
// Add the contents of a to the array
for { let i := 0 } lt(i, loopsa) { i := add(1, i) } { mstore(add(m, mul(32, add(1, i))), mload(add(a, mul(32, add(1, i))))) }
// Add the contents of b to the array
for { let i := 0 } lt(i, loopsb) { i := add(1, i) } { mstore(add(m, add(mul(32, add(1, i)), alen)), mload(add(b, mul(32, add(1, i))))) }
mstore(0x40, add(m, add(32, totallen)))
c := m
}
}
I'm new to ethereum programming, so there may be a mistake or some clear optimizations that can be made, but I tested this code in Remix.
For 2 5 bytes arrays it costed about 1500 gas, with 2 larger (~ 40 bytes long) bytes arrays it costed about 1700 gas. It looks to be about a 100 gas increase per 32 bytes.
Please let me know if there are any clear optimizations as I'm using this in my own contract!
Edit:
I made a change in the algorithm as it didn't work for byte arrays >32 bytes long.
Your data should be returned abi.encoded. In your case, since you are dealing with a dynamic type, you must include the offset data start (see this answer)
Modify your function for this, and it should work :
function nowItWorks() external view returns (bytes memory) {
assembly {
let res := mload(0x40) // get free memory pointer
mstore(res, 0x20) // return data offset : abi encoding
mstore(add(res, 0x20), 0x40) // length: 64 bytes
mstore(add(res, 0x40), 1) // first 32 bytes
mstore(add(res, 0x60), 2) // second 32 bytes
mstore(0x40, add(res, 0x80)) // update free memory pointer
return (res, 0x80) // return 128 bytes (32 bytes data offset + 32 bytes length + 64 bytes data)
}
}
Since your function is external, and you are returning (and exiting the contract) from the function, you can also choose not to respect solidity memory layout and remove all code related to the free memory pointer :
function nowItWorks() external view returns (bytes memory) {
assembly {
let res := 0x00 // Take any memory location
mstore(res, 0x20) // return data offset : abi encoding
mstore(add(res, 0x20), 0x40) // length: 64 bytes
mstore(add(res, 0x40), 1) // first 32 bytes
mstore(add(res, 0x60), 2) // second 32 bytes
return (res, 0x80) // return 128 bytes (32 bytes data offset + 32 bytes length + 64 bytes data)
}
}
Best Answer
Building up on @Jesbus comment, here is a commented example achieving what you want :
EDIT :
I'm adding an example relying on array literals where there is no length prefix and using bytes32.
Plus a third example not relying on solidity arrays but directly allocating / computing in memory through assembly.