0

I would like to take three uints and essentially concatenate them together as one bytes variable. What's the simplest way I can do that? I've seen many posts about people writing full-blown code to merge two bytes together. And that's great, but in my case, if I want to merge 3, I'd have to call that merge function twice which seems inefficient.

I'm familiar with this method which essentially converts a uint to a byte in the simplest way possible.

function toBytes(uint256 x) returns (bytes b) {
    b = new bytes(32);
    assembly { mstore(add(b, 32), x) }
}

I feel like I should be able to apply this technique to what I'm trying to do. But I haven't been able to figure out how. This method is basically taking one single uint and viewing it as a bytes, which is exactly what I want. Except I would like to say "the first x bytes belong to this uint, and the second x bytes belong to that uint, and the third x bytes belong to this last uint.

Edit: Here's what I have so far. The following code generates this result. Now I just need to figure out how to remove all the extra 0's I do not want.

0x00000000000000000000000000000000000000000000000000000000deadbeefdeadbeefdeadbeef

function testStuff(
    uint256 x1, // 3735928559 -> "0xdeadbeef"
    uint256 x2, // 3735928559 -> "0xdeadbeef"
    uint256 x3 // 3735928559 -> "0xdeadbeef"
    ) public returns (bytes memory b) 
{
    b = new bytes(40);
    assembly 
    { 
        mstore(add(b, 40), x1)
        mstore(add(b, 36), x2)
        mstore(add(b, 32), x3)
    }
}

I want to end up with exactly

0xdeadbeefdeadbeefdeadbeef

I'm not sure if this is the right way to do this. But I'm basically using an expanded bytes object of size greater than 32 so I have enough space to lay out each uint as a byte. Based on the size I set them next to each other. But now I end up with all this extra space that I need to remove.

1 Answer 1

1
function testStuff(
    uint256 x1, // 3735928559 -> "0xdeadbeef"
    uint256 x2, // 3735928559 -> "0xdeadbeef"
    uint256 x3 // 3735928559 -> "0xdeadbeef"
    ) public returns (bytes memory b) 
{
    b = new bytes(40);
    assembly 
    { 
        mstore(add(b, 12), x1)
        mstore(add(b, 8), x2)
        mstore(add(b, 4), x3)
        mstore(b, 12)
    }
}

will give you the result you are looking for. The data type bytes stores the length of itself as the first argument. So by adding mstore(b, 12), we instruct the EVM to store 12 at the beginning of the bytes meaning it will contain 12 bytes.

However, this only works for your example with a uint of 3735928559 (or anything below 2^32 = uint32). If you wanted to support all uint256, you will have to change it to 32 bytes per uint. And for lower numbers you will of course then get spaces with 0 inbetween.

Update: I don't know why I made it so difficult. Instead of mstore(b, 12) you can also just write b = new bytes(12); of course.

5
  • Interesting. So I actually tried something similar (without that last line) and the result was "bytes: b 0x" which makes sense now seeing your explanation of what that last line does. Yeah my actual values i'll be using for this will be much larger than the 0xdeadbeef example.
    – LampShade
    Apr 14, 2020 at 15:02
  • This works perfectly! I tried it with my real data and it's working as expected. Yes, I understand those hardcoded numbers were for only the 0xdeadbeef example while I was trying to figure out what I was doing wrong.
    – LampShade
    Apr 14, 2020 at 15:14
  • Regarding your comment on "I don't know why I made it so difficult" I fell into that same trap but I recall there being a reason why I did that as well. When I was testing with larger numbers for some reason I ended up getting a 0x0 as a result. But when I initialized the bytes with a much larger size, that problem went away. It's also possible I was making other mistakes somewhere though.
    – LampShade
    Apr 14, 2020 at 15:17
  • Yep, confirmed, You still need the mstore(b, 12) part if you're using a very large byte. For example, I just did a test with a size of 70 and I had to do mstore(b, 70) at the end or else the result was 0x0 instead of my actual expected result.
    – LampShade
    Apr 14, 2020 at 15:24
  • You do need to do mstore(b, 12) as all the three int storing operations overwrite the length in the last byte of 32 bytes referred to by b and we have 0 length if we do not reset. The reason is that EVM writes 32 bytes in memory at a time and each integer storing operations have only 4 bytes occupied on the right side and the rest are zero which overwrite the length on each write operation. "0.....31 bytes 00deadbeefdeadbeefdeadbeef" This is the representation of b. The last byte of the first 32 bytes have length which is 00 because of integer writing operations. Aug 14, 2020 at 23:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.