0

When a contract calls itself using the .call method and attempts to pass a struct, it results in a revert with the following error output:

"error": "Failed to decode output: Error: data out-of-bounds (length=0, offset=32, code=BUFFER_OVERRUN, version=abi/5.7.0)

An example of a contract that contains this error:

pragma solidity >=0.7.0 <0.9.0;

library Dict {
    struct DictType {
        int256 max_length;
        int256 current_length;
    }
}

contract Test {
    function test() public returns (int256) {
        Dict.DictType memory dict = Dict.DictType({max_length: 1, current_length: 1});
        
        (bool test, bytes memory res) = address(this).call(abi.encodeWithSignature("temp(Dict.DictType)", dict));
        require(test);

        (int256 resDecoded) = abi.decode(res, (int256));
        return resDecoded;
    }

    function temp(Dict.DictType memory dict) public pure returns (int256) {
        return 19;
    }
} 

When the called method is in a library or other contract, or a base type like int256 is passed instead of a struct, no error occurs. I am aware that this is far from standard practice, but for my purposes I need to be able to use the .call method in a contract to call itself while passing a struct. Is there any way to accomplish this?

1 Answer 1

1

Instead of abi.encodeWithSignature(), you can use abi.encode() directly to encode the struct. Then, you would need to know the function selector (function signature hash) manually, that you can get using this.functionName.selector.

As Riley has mentioned in the comments, it also works when using abi.encodeWithSelector(), even without the need of encoding struct.

Here's the revised code:

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

library Dict {
    struct DictType {
        int256 max_length;
        int256 current_length;
    }
}

contract Test {
    function testFunction_UsingEncodePacked() public returns (int256) {
        Dict.DictType memory dict = Dict.DictType({max_length: 1, current_length: 1});

        // Encode the struct manually
        bytes memory data = abi.encode(dict);
        
        // Manually calculate the function selector (function signature hash)
        bytes4 selector = this.temp.selector;

        // Invoke the function using address(this).call()
        (bool success, bytes memory result) = address(this).call(abi.encodePacked(selector, data));
        require(success, "Function call failed");

        int256 resDecoded = abi.decode(result, (int256));
        return resDecoded;
    }

    function testFunction_UsingEncodeWithSelector() public returns (int256) {
        Dict.DictType memory dict = Dict.DictType({max_length: 1, current_length: 1});

        // Manually calculate the function selector (function signature hash)
        bytes4 selector = this.temp.selector;

        // Invoke the function using address(this).call()
        (bool success, bytes memory result) = address(this).call(abi.encodeWithSelector(selector, dict));
        require(success, "Function call failed");

        int256 resDecoded = abi.decode(result, (int256));
        return resDecoded;
    }

    function temp(Dict.DictType memory /*_dict*/) public pure returns (int256) {
        return 19;
    }
}
2
  • 1
    Thanks for your quick response, the solution you provided works like a charm. I did find something rather weird while testing, it turns out that using abi.encodeWithSelector instead of abi.encodeWithSignature to encode the call will also work. It is my understanding that these 2 should function in the same way, pretty much only differing in how the function to be called is passed, am I mistaken in this?
    – Riley
    Commented Apr 21 at 11:14
  • Yes, you're right. I've added that in my answer. Please check. Commented Apr 21 at 11:40

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.