Solidity Assembly – How to Replace Slices of Bytes with Assembly

assemblysolidity

I brand new to assembly, and just at the point of writing my first code using it, so please excuse me if I am way off on what I am trying to accomplish.

Here is my code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;


contract Test {
    function starter() public pure returns(string memory, bytes memory) {
        bytes6 _id = '123456';
        bytes memory b = 'The input _id is ______ and it was replaced using assembly';

        assembly {
            mstore(add(b, 49), _id)
        }

        return (string(b), b);
    }
}

The output looks like this:

0:
string: The input _id is 123456 assembly
1:
bytes: 0x54686520696e707574205f696420697320313233343536000000000000000000000000000000000000000000000000000020617373656d626c79

So I can tell that what it did was store the 6 bytes of _id, plus another 26 null bytes for a total of 32 bytes replaced.

Is it possible to do this without those additional 26 null bytes? Note I am not looking to do this iteratively as the real case will be a much larger string with many more replacements.

Best Answer

Is it possible to do this without those additional 26 null bytes? Note I am not looking to do this iteratively as the real case will be a much larger string with many more replacements.

As I said in the comments, it's totally possible. Here is a simple version working only with bytes6, let me know if you are interested in a more generic "slice assignment" piece of code handling various lengths.

The idea is simply to clear the leading first 6 bytes of the existing data with the and operator and the appropriate mask (0x000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF in this case, equivalent to not(shl(208, 0xFFFFFFFFFFFF))) and then combining your id (which is actually a left aligned 32 bytes value) with the result of the previous operation with the or operator.

To put it simply, it replace the first 6 bytes at add(b, 49 by the first 6 bytes of _id. All other bytes are left unchanged.

assembly {
    mstore(add(b, 49), or(and(mload(add(b, 49)), not(shl(208, 0xFFFFFFFFFFFF))), _id))
}

The result is 0x54686520696e707574205f69642069732031323334353620616e6420697420776173207265706c61636564207573696e6720617373656d626c79, in ASCII : "The input _id is 123456 and it was replaced using assembly".

I hope this answers your question.