6

Does anyone know how to efficiently concat two bytes arrays with assembly language to save gas costs? Currently I have

function mergeBytes(bytes param1, bytes param2) returns (bytes) {

    bytes memory merged = new bytes(param1.length + param2.length);

    uint k = 0;
    for (uint i = 0; i < param1.length; i++) {
        merged[k] = param1[i];
        k++;
    }

    for (i = 0; i < param2.length; i++) {
        merged[k] = param2[i];
        k++;
    }
    return merged;
}

Is there a way to avoid for loops here?

Tomas Gobionis
  • 103
  • 1
  • 3

2 Answers2

4

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.

4

As can be found in solidity-examples repository provided from Foundation, it goes like this:

function concat(bytes memory self, bytes memory other)
returns (bytes memory) {
      bytes memory ret = new bytes(self.length + other.length);
      var (src, srcLen) = Memory.fromBytes(self);
      var (src2, src2Len) = Memory.fromBytes(other);
      var (dest,) = Memory.fromBytes(ret);
      var dest2 = dest + srcLen;
      Memory.copy(src, dest, srcLen);
      Memory.copy(src2, dest2, src2Len);
      return ret;
}

https://github.com/ethereum/solidity-examples

oniki
  • 41
  • 1
  • 1
    on line var dest2 = dest + src2Len, shouldn't it be instead var dest2 = dest + srcLen. Notice the change from src2Len to srcLen? – Marcelo Fornet May 09 '21 at 23:57