2

I've used the code from the solidity assembly tutorial here to create a single dimensional array in assembly. The code is as follows:

contract C {
function f(uint a, uint b) constant returns (uint[]) {
    assembly {
        // Create an dynamic sized array manually.
        let memOffset := mload(0x40) // 0x40 is the address where next free memory slot is stored in Solidity.
        mstore(memOffset, 0x20) // single dimensional array, data offset is 0x20
        mstore(add(memOffset, 32), 2) // Set size to 2
        mstore(add(memOffset, 64), a) // array[0] = a
        mstore(add(memOffset, 96), b) // array[1] = b
        return(memOffset, 128)
    }
}

}

When I run the function by itself in Remix, everything is ok, and it outputs the array in the correct format. The issue occurs when I try to index any value in the array when it's returned from another function. It seems to return an array of size 32, with all elements set to 32.

function get_f(uint a, uint b) public returns(uint){
        uint[] ret = f(a,b);
        return(ret[0]); //should return a, instead returns 32.
    }

The above demonstrates the problem more clearly, but I am in fact using this method of array creation to cheaply convert between uint and byte arrays directly in memory (for use with the new precompiled modexp contract). thanks.

1 Answer 1

4

The author of the github page that you referenced didn't update the free memory pointer after allocating the memory. Also as Tjaden Hess noted in the comments

return as an opcode and return as a Solidity statement are very different. The opcode causes the entire contract execution to return at that point, with the return value that you designate in memory. The Solidity return just pops off the call frame and returns to the calling function

Below is the correct code:

contract C {
    function f(uint a, uint b) pure public returns (uint[] memory memOffset) {
        assembly {
             // Create an dynamic sized array manually.
             // Don't need to define the data type here as the EVM will prefix it
             memOffset := msize() // Get the highest available block of memory
             mstore(add(memOffset, 0x00), 2) // Set size to 2
             mstore(add(memOffset, 0x20), a) // array[0] = a
             mstore(add(memOffset, 0x40), b) // array[1] = b
             mstore(0x40, add(memOffset, 0x60)) // Update the msize offset to be our memory reference plus the amount of bytes we're using
        }
    }

    function get_f(uint a, uint b) public returns(uint){
        uint[] memory ret = f(a,b);
        return ret[0];
    }
}

I created a pull request to fix this on Github https://github.com/androlo/solidity-workshop/pull/5

4
  • 2
    Another part of the issue is that return as an opcode and return as a Solidity statement are very different. The opcode causes the entire contract execution to return at that point, with the return value that you designate in memory. The Solidity return just pops off the call frame and returns to the calling function Commented Nov 8, 2017 at 18:08
  • @TjadenHess Thanks for pointing this out. I included it in the answer. Commented Nov 8, 2017 at 18:30
  • @TjadenHess btw do you know why in the original code 4 words of memory are allocated and returned? Commented Nov 8, 2017 at 18:40
  • 2
    In the original, it is using ABI encoding, which is correct when you are actually return-ing the value. The array itself is contained in 3 words, but the first word points to the beginning of the data, as specified in solidity.readthedocs.io/en/develop/… Commented Nov 8, 2017 at 18:44

Your Answer

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

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