Solidity Inline Assembly – How to Return Bytes

assemblybytessolidity

I have two little tests in which I'm trying to return bytes that are populated via inline assembly. It works when I'm using a pre-defined return variable, but I'm getting a buffer overrun when I try to do it all in inline assembly. What am I missing here?

pragma solidity ^0.8.4;

contract AssemblyTest {

    function working() external view returns (bytes memory res) {
        assembly {
            res := mload(0x40)           // get free memory pointer
            mstore(res, 0x40)            // length: 64 bytes
            mstore(add(res, 0x20), 1)    // first 32 bytes
            mstore(add(res, 0x40), 2)    // second 32 bytes
            mstore(0x40, add(res, 0x60)) // update free memory pointer
        }
    }

    function notWorking() external view returns (bytes memory) {
        assembly {
            let res := mload(0x40)       // get free memory pointer
            mstore(res, 0x40)            // length: 64 bytes
            mstore(add(res, 0x20), 1)    // first 32 bytes
            mstore(add(res, 0x40), 2)    // second 32 bytes
            mstore(0x40, add(res, 0x60)) // update free memory pointer
            return (res, 0x60)           // return 96 bytes (32 bytes length + 64 bytes data)
        }
    }
}

Best Answer

Your data should be returned abi.encoded. In your case, since you are dealing with a dynamic type, you must include the offset data start (see this answer)

Modify your function for this, and it should work :

    function nowItWorks() external view returns (bytes memory) {
        assembly {
            let res := mload(0x40)       // get free memory pointer
            mstore(res, 0x20)            // return data offset : abi encoding
            mstore(add(res, 0x20), 0x40) // length: 64 bytes
            mstore(add(res, 0x40), 1)    // first 32 bytes
            mstore(add(res, 0x60), 2)    // second 32 bytes
            mstore(0x40, add(res, 0x80)) // update free memory pointer
            return (res, 0x80)           // return 128 bytes (32 bytes data offset + 32 bytes length + 64 bytes data)
        }
    }

Since your function is external, and you are returning (and exiting the contract) from the function, you can also choose not to respect solidity memory layout and remove all code related to the free memory pointer :

      function nowItWorks() external view returns (bytes memory) {
        assembly {
            let res := 0x00              // Take any memory location
            mstore(res, 0x20)            // return data offset : abi encoding
            mstore(add(res, 0x20), 0x40) // length: 64 bytes
            mstore(add(res, 0x40), 1)    // first 32 bytes
            mstore(add(res, 0x60), 2)    // second 32 bytes
            return (res, 0x80)           // return 128 bytes (32 bytes data offset + 32 bytes length + 64 bytes data)
        }
    }
Related Topic