1

I need some help understanding assembly functions

pragma solidity ^0.4.23;
contract Test {
    function addTest() public pure returns (uint c) {
    bytes memory b = new bytes(1024);
    assembly { c:=add(b, 0x20) }
}
}

In this snippet, what does adding a number and a bytes array mean?!

Also whatever the size of the bytes variable I take, the result of the function is consistently 160. Which means that b is somehow evaluated to 128? why is b taken as 128 in the add function argument Any help appreciated. Thanks!

0

1 Answer 1

2

What this smart contract in function addTest() does is:

  1. It initializes a byte array in memory (volatile) with a size of 1024 bytes
  2. assembly: It takes the address of the byte array, not the content but the address where you can find its content in memory, and adds 0x20 to it
  3. the result of (memory location of) b + 0x20 is stored in c

Why is b evaluated to 128? As you know at this point, the smart contract has taken the address of b for the calculation. The address 0x80 (128) has a special purpose. First you have to know that the allocation of memory in the EVM works by reading the value in the address 0x40 (the ,,free memory pointer''), adding the amount of bytes you require and storing it in 0x40 again. So 0x40 contains an address which tells us where free memory begins (for more detail take a look at Ethereum opcode: meaning of first few instructions?). After your smart contract was initialized by the code which was additionally inserted by the compiler, the free memory pointer at 0x40 is set to 0x80. In other words, when your smart contract starts to do its work the first memory location it can use is 0x80 (https://solidity.readthedocs.io/en/v0.4.25/miscellaneous.html#layout-in-memory). This is why you always get the result 160, 0xa0 = 0x80 + 0x20

I have provided an example for you which initializes three bytes of the bytearray b and reads them in assembly into the returning values:

pragma solidity ^0.4.25;

contract Test {
    function addTest() public pure returns (byte c1, byte c2, byte c3) {
        // create bytes array and init first byte
        bytes memory b = new bytes(169);
        b[0] = byte(59);
        b[1] = byte(42);
        b[2] = byte(99);

        assembly {
            // load byte 0-31 to read b[0]
            // note: first 32 bytes contain the array length
            c1 := mload(add(b, 0x20))
            // load byte 1-32 to read b[1]
            c2 := mload(add(b, 0x21))
            // load byte 2-33 to read b[2]
            c3 := mload(add(b, 0x22))
        }
    }
}

In the previous example you can see that I begin to read data from b at the address b + 0x20 . This is because the length of the bytearray is stored at the first 32 bytes of address b.

As you can see I load 32 bytes every time I want to read a single byte. Since the mload(...) instruction is cheap regarding the gas cost, it is not that critical to do so. Nevertheless, it is kind of expensive regarding the runtime. You can optimize the code by masking the first 32 byte to extract every desired byte within:

pragma solidity ^0.4.25;

contract Test {
    function addTest() public pure returns (byte c1, byte c2, byte c3) {
        // create bytes array and init first byte
        bytes memory b = new bytes(169);
        b[0] = byte(59);
        b[1] = byte(42);
        b[2] = byte(99);

        assembly {
            // load byte 0-31 to read b[0]
            // note: first 32 bytes contain the array length
            let data32b := mload(add(b, 0x20))
            c1 := data32b
            // read b[1] from byte 0-31
            c2 := mul(data32b, 256)
            // read b[2] from byte 0-31
            c3 := mul(data32b, exp(256, 2))
        }
    }
}

When you work with the sload(...) command to load data from storage (persistent), keep in mind to use the second variant, never use the first variant for this case. This is because sload(...) is very expensive in terms of gas and runtime.

One last note: When your EVM is compatible with the constantinople version, you can replace the exp(...) with shl(...) (shift left), which should save you some gas as well:

pragma solidity ^0.4.25;

contract Test {
    function addTest() public pure returns (byte c1, byte c2, byte c3) {
        // create bytes array and init first byte
        bytes memory b = new bytes(169);
        b[0] = byte(59);
        b[1] = byte(42);
        b[2] = byte(99);

        assembly {
            // load byte 0-31 to read b[0]
            // note: first 32 bytes contain the array length
            let data32b := mload(add(b, 0x20))
            c1 := data32b
            // note: In constantinople you can use the following instead:
            // read b[1] from byte 0-31
            c2 := shl(data32b, 8)
            // read b[2] from byte 0-31
            c3 := shl(data32b, mul(8, 2))
        }
    }
}
0

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.