1

I'm quite confused of how the bytes-utils library's slice function. I have a few questions about how it works, especially about the code below. The full code can be found here.

  1. Why is tempBytes declared then assigned the free memory pointer? Isn't it possible to do return(0x40, length) thus saving gas?
  2. Why is lengthmod variable needed?
  3. What is the mc variable, and why is it specifically being multiplied by 0x20 and not another number?
  4. What exactly does the for loop do here?
  5. Why does the free memory pointer need to be updated? How does that happens if it's not?

Thanks!


bytes memory tempBytes;
assembly {
    tempBytes := mload(0x40)
    
    // The first word of the slice result is potentially a partial
    // word read from the original array. To read it, we calculate
    // the length of that partial word and start copying that many
    // bytes into the array. The first word we copy will start with
    // data we don't care about, but the last `lengthmod` bytes will
    // land at the beginning of the contents of the new array. When
    // we're done copying, we overwrite the full first word with
    // the actual length of the slice.
    let lengthmod := and(_length, 31)
    
    // The multiplication in the next line is necessary
    // because when slicing multiples of 32 bytes (lengthmod == 0)
    // the following copy loop was copying the origin's length
    // and then ending prematurely not copying everything it should.
    let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
    let end := add(mc, _length)
    
    for {
        // The multiplication in the next line has the same exact purpose
        // as the one above.
        let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
    } lt(mc, end) {
        mc := add(mc, 0x20)
        cc := add(cc, 0x20)
    } {
        mstore(mc, mload(cc))
    }
    
    mstore(tempBytes, _length)
    
    //update free-memory pointer
    //allocating the array padded to 32 bytes like the compiler does now
    mstore(0x40, and(add(mc, 31), not(31)))
}

return tempbytes;

1 Answer 1

2

Quite a lot of questions and I have to say the original code is not really good commented.

1.) The assembly codes follows the memory layout/ behaviour of Solidity (see Solidity in Memory). The free mem pointer (aka 0x40) contains a pointer to the start of the free memory region. By loading this into the tempBytes it is indicated that the content of this variable is stored in this location. This is similar to memory pointers in other pointer based languages.

5.) At the end the free memory pointer is then increased to point behind the tempBytes, so that new memory will by allocated in the correct region by other Solidity code. If you would not update the free mem pointer, new memory allocations would overwrite your slice.

2.) lengthmod is the module of the length with 32. This is required as memory is read in words. 1 word is equal to 32 bytes (0x20 in hex). Therefore a lot of operations are based on 32 steps. If you think about a bytes in Solidity you can imagine it as an array of bytes32 that are concatenated. So if you handle bytes where the length is not a multiple of 32, some special logic has to be applied. Calculating the modulo of 32 in assembly can be done with and(_length, 31) and this is then stored in lengthmod.

The code wants to avoid using more memory than required. As everything happens in 32 bytes operation when reading and writing memory the code offsets the beginning to avoid overrunning the end in case an amount is sliced that is not a multiple of 32 (this is important for 3).

3.) Here the special handling mentioned before is applied. As mentioned before our bytes are stored similar to an array of bytes32. For this we also store the length of the bytes. In memory we store it as <32 bytes for length><data (e.g. bytes32 array)>. As mentioned in 2) the code avoids using more memory than required we offset it the beginning (so copy more from the start). The exact start in memory is stored in mc and cc. mc is for the memory of the slice and cc for the memory of the original data.

If lengthmod is 0, then it is not necessary to offset the start of the memory region. In this case 32 (0x20 in hex) is added, so that the data is written after the memory region of the length of the bytes.

4.) The syntax of the for loop is for {<init_block>} <condition_block> {<post_iteration_block>} {<body_block>}. As mentioned before bytes are like an array of bytes32. Then when slicing the bytes this "array" is iterated and the sliced data is copied to a new "array" (tempBytes). mc is the index of the new array and cc is the index of the source array. First cc is initialized to point at the start of the data that should be sliced. On each iteration it is checked if already enough data was copied. After each iteration mc and cc are increased by 32 (e.g. looking at the next entry in our bytes "array"). Inside the body of the loop the data is then copied. For this the part of the slice is loaded from source memory (mload(cc)) and then stored in the new memory (mstore(mc, ...)).

Edit: I tried to add some comments to the code inline: https://gist.github.com/rmeissner/76d6345796909ee41fb9f36fdaa4d15f

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.