I would like to make an external call with assembly, but am having trouble retrieving the return data as "bytes". The code below currently returns only 32 bytes, which just shows the length of the returned data, but not the actual byte array. Also, it seems that I need to return outside of the assembly code lines, since the return in assembly will halt execution entirely and return.

How can I copy the return data from the assembly call to the bytes object "data" which is instantiated outside of the assembly lines?

function extCall(address calledContract, bytes memory callData) internal returns (bytes memory) {
        bytes memory data;
        assembly {
            let success := call(gas(), calledContract, 0, add(callData,0x20), mload(callData), 0, 0)
            if eq(success, 0) {
                revert(0, 0)
            mstore8(data, returndatasize())
            returndatacopy(data, 0, returndatasize())
            // cannot return in assembly: execution will halt.
        return data;
  • 1
    Do you really have to use assembly? Since v0.5.0, Solidity returns both a boolean success flag and the return data as bytes memory when doing a .call, .delegatecall or .staticcall. Read the docs. Commented Aug 30, 2021 at 15:44

1 Answer 1


Here's how you can return a bytes of dynamic (unknown prior to execution) length with Solidity Yul (assembly):

pragma solidity ^0.8.13;

contract Test {
    uint256 public value1;
    uint256 public value2;
    uint256 public value3;

    string public name;

    function extCall(address calledContract, bytes memory callData) public payable returns (bool _success, bytes memory) {
            uint256 _value = msg.value;
            bytes memory _returnData;

            uint256 _toCopy;

            assembly {
                _success := call(
                    gas(), // gas
                    calledContract, // recipient
                    _value, // ether value
                    add(callData, 0x20), // inloc
                    mload(callData), // inlen
                    0, // outloc
                    0 // outlen
                // limit our copy to 256 bytes
                _toCopy := returndatasize()

                // Store the length of the copied bytes
                mstore(_returnData, _toCopy)
                // copy the bytes from returndata[0:_toCopy]
                returndatacopy(add(_returnData, 0x20), 0, _toCopy)

    function justTest() external returns (uint256 result) {
        (bool success, bytes memory returnData) = extCall(address(this), abi.encodeWithSelector(Test.abc.selector, uint256(25677), uint256(73), uint256(41), "Hello!")); // extCall(address(this), abi.encodeWithSelector(Test.abc.selector, 1));
        require(success, "The external call failed");
        (result) = abi.decode(returnData, (uint256));

    function abc(uint256 _value1, uint256 _value2, uint256 _value3, string calldata _name) external returns (uint256) {
        require(_value1 > 0 && _value2 > 0 && _value3 > 0, "Not all required args passed in");
        value1 = _value1;

        value2 = _value2;

        value3 = _value3;

        name = _name;
        return _value1 + _value2 + _value3;

... Which results in the following output:



This code snippet that I wrote doesn't differ much from your code, but I believe the key here is this line:

returndatacopy(data, 0, returndatasize())


returndatacopy(add(_returnData, 0x20), 0, _toCopy)

Otherwise, the return bytes are truncated and obfustrated. And that's why the result looks corrupted, like this:

{ "0": "uint256: result 196" }

While the input remained the same during both transactions!

Hopefully, it answers your question :)

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.