39

Is there an easy way to convert a uint to bytes in Solidity?

eth
  • 85,679
  • 53
  • 285
  • 406

7 Answers7

32

The alternative to @eth's answer is to use assembly:

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

This is significantly more gas-efficient, but depends on the internal memory layout used by the Solidity compiler. In theory, this can change in future, but in practice it should be fairly stable.

Nick Johnson
  • 8,144
  • 1
  • 28
  • 35
  • 1
    Would you mind explaining what each line does? What is the purpose of this add(b, 32). Couldn't we simply do mstore(b, x)? – ppoliani Nov 24 '20 at 21:46
  • @ppoliani did you get an answer to your question? I am also curious why we add 32 to b. (when b should be at the start of memory address anyway). – skgbanga May 31 '21 at 19:12
  • Actually it is explained here: https://ethereum.stackexchange.com/questions/98750/what-does-addsource-32-do-in-solidity – skgbanga May 31 '21 at 19:23
  • 1
    @ppoliani the bytes type in Solidity is stored in memory as: 1) first 32 bytes = length of the bytes value, 2) then the bytes value itself. The mstore opcode stores 32 bytes, starting from a certain offset (in the bytes value to grab) to a specified location in memory. So the instruction mstore(add(b, 32), x) can be translated in plain words as "stores 32 bytes at position x in memory, starting from the 32nd bytes offset in the b" (so to skip the length, and just store b itself). – CJ42 Dec 13 '21 at 00:01
24

It seems there now is, since solidity version 0.4.24 you can use abi.encodePacked

E.G:

uint i = 0;
i_bytes = abi.encodePacked(i);
PiersyP
  • 396
  • 2
  • 4
18

Here's a comparison of the gas used in the three methods by @NickJohnson, @Eth and @k26dr. I've added a constant to the function modifiers as these functions do not alter the blockchain:

pragma solidity ^0.4.2;

contract Test {
    function toBytesNickJohnson(uint256 x) constant returns (bytes b) {
        b = new bytes(32);
        assembly { mstore(add(b, 32), x) }
    }

    function toBytesEth(uint256 x) constant returns (bytes b) {
        b = new bytes(32);
        for (uint i = 0; i < 32; i++) {
            b[i] = byte(uint8(x / (2**(8*(31 - i))))); 
        }
    }

    function toBytesNicolasMassart(uint256 x) constant returns (bytes c) {
        bytes32 b = bytes32(x);
        c = new bytes(32);
        for (uint i=0; i < 32; i++) {
            c[i] = b[i];
        }
    }    
}

You can see the gas cost of running these functions in Remix (Solidity Browser):

enter image description here

Alex Papageorgiou
  • 670
  • 1
  • 5
  • 14
BokkyPooBah
  • 40,274
  • 14
  • 123
  • 193
7

There are no easy ways to convert anything to bytes. Here's a function:

function toBytes(uint256 x) returns (bytes b) {
    b = new bytes(32);
    for (uint i = 0; i < 32; i++) {
        b[i] = byte(uint8(x / (2**(8*(31 - i))))); 
    }
}

Based on Solidity Gitter chat.

eth
  • 85,679
  • 53
  • 285
  • 406
5

If you're overly concerned about gas we can improve on @NickJohnson answer too.

function toBytes(uint _num) returns (bytes _ret) {
    assembly {
        _ret := mload(0x10)
        mstore(_ret, 0x20)
        mstore(add(_ret, 0x20), _num)
    }
}

This will shave approximately a further 15% off the gas cost, just be careful that you're not using that memory for something else as 0x10 is a direct reference to memory.

Also, if you want to copy an 8bit int to byte here's an alternative which only uses half the gas cost:

function toByte(uint8 _num) returns (byte _ret) {
    assembly {
        mstore8(0x20, _num)
        _ret := mload(0x20)
    }
}

Again, be mindful that 0x20 is another direct memory reference and in all honesty with this one I would stick with

function toByte(uint8 _num) returns (byte _ret) {
    return byte(_num);
}

The gas price between the two was almost identical, but the functional ASM was undercutting it by about 70 wei

Edit: If you're concerned about overwriting memory you could use:

let m_alloc := add(msize(),0x1)

Instead of referencing memory yourself

James Lockhart
  • 514
  • 4
  • 7
  • I didn't know msize() can be used to increase the memory allocation by adding to it. The solidity assembly document (https://solidity.readthedocs.io/en/v0.4.24/assembly.html?highlight=calldatacopy) is very brief. Is there a better reference document out there? – Thomas Jul 20 '18 at 23:04
  • msize doesn't, it returns the highest point in memory and you simply state that you want to write to msize() plus 1. Here's another stack page where I explain it in more detail https://ethereum.stackexchange.com/questions/9537/how-to-return-dynamic-sized-arrays-original-address-on-the-memory-of-solidity – James Lockhart Jul 23 '18 at 16:20
2

You can convert to bytes32 then convert to bytes:

uint u = 200;
bytes32 b = bytes32(u);
bytes memory c = new bytes(32);
for (uint i=0; i < 32; i++) {
    c[i] = b[i];
}
Nicolas Massart
  • 6,783
  • 2
  • 29
  • 63
k26dr
  • 861
  • 7
  • 12
2

You can avoid padding the bytes array with 0's with the toBytes implementations by determining the scriptNumSize

function scriptNumSize(uint256 i) public view returns (uint256) {
    if      (i > 0x7fffffff) { return 5; }
    else if (i > 0x7fffff  ) { return 4; }
    else if (i > 0x7fff    ) { return 3; }
    else if (i > 0x7f      ) { return 2; }
    else if (i > 0x00      ) { return 1; }
    else                     { return 0; }
}

function toBytes(uint256 x) public view returns (bytes memory b) {
    uint a = scriptNumSize(x);
    b = new bytes(a);
    for (uint i = 0; i < a; i++) {
        b[i] = byte(uint8(x / (2**(8*(a - 1 - i))))); 
    }
}

For example uint 1563384765 would return 0x5d2f5bbd not 0x000000000000000000000000000000000000000000000000000000005d2f5bbd

This was written specifically for timestamps. If you want larger numbers you can add more if cases.