Solidity: UnimplementedFeatureError: calldata structs

Created on 9 Jun 2020  路  13Comments  路  Source: ethereum/solidity

I notified @chriseth in the Gitter chat that I am getting a lot of UnimplementedFeatureError from the solc-js compiler 0.6.9, in places where I want to make use of calldata structs. It seems like the Error is thrown when I want to pass around such calldata structs between functions internally. The calldata structs are nested.

Environment

  • Compiler version: 0.6.9

Steps to Reproduce

In this issue I will include one such example of the UnimplementedFeatureError: for a function that I was able to distill down a lot. In this case the error comes from a calldata array of the nested struct.

I will include more examples, as soon as our repo is public again. The other examples I have are inside large functions that I won't distill down now.

Try to compile the following contract:

// "SPDX-License-Identifier: UNLICENSED"
pragma solidity ^0.6.9;
pragma experimental ABIEncoderV2;

struct Condition {
    address inst;  // can be AddressZero for self-conditional Actions
    bytes data;  // can be bytes32(0) for self-conditional Actions
}

struct Task {
    Condition[] conditions;  // optional
    uint256 selfProviderGasPriceCeil;
}

struct TaskReceipt {
    uint256 id;
    Task[] tasks;
    uint256 expiryDate;
}

contract CalldataFeatureErrors {
    // taskReceipt.id => taskReceiptHash
    mapping(uint256 => bytes32) public taskReceiptHash;

    function cancelTask(TaskReceipt memory _TR) public {
        // Effects
        bytes32 hashedTaskReceipt = hashTaskReceipt(_TR);
        require(
            hashedTaskReceipt == taskReceiptHash[_TR.id],
            "GelatoCore.cancelTask: invalid taskReceiptHash"
        );
        delete taskReceiptHash[_TR.id];
    }

    function multiCancelTasks(TaskReceipt[] calldata _taskReceipts) external {
        for (uint i; i < _taskReceipts.length; i++) cancelTask(_taskReceipts[i]);
    }

    function hashTaskReceipt(TaskReceipt memory _TR) public pure returns(bytes32) {
        return keccak256(abi.encode(_TR));
    }
}
bug unimplemented error

All 13 comments

Slimmed it down a bit more:

// "SPDX-License-Identifier: UNLICENSED"
pragma solidity ^0.6.9;
pragma experimental ABIEncoderV2;


struct TaskReceipt {
    uint[] tasks;
}

contract CalldataFeatureErrors {
    function cancelTask(TaskReceipt memory _TR) public {}
    function multiCancelTasks(TaskReceipt[] calldata _taskReceipts) external {
        cancelTask(_taskReceipts[0]);
    }

}

What seems to be unimplemented in the copying of nested arrays / structs from calldata to memory.

Here is one more, that seems to indicate that the copying of nested structs (not inside an array) from calldata to memory is also not implemented.

Try to compile this:

// "SPDX-License-Identifier: UNLICENSED"
pragma solidity ^0.6.9;
pragma experimental ABIEncoderV2;

struct Condition {
    address inst;  // can be AddressZero for self-conditional Actions
    bytes data;  // can be bytes32(0) for self-conditional Actions
}

struct Task {
    Condition[] conditions;  // optional
    uint256 selfProviderGasPriceCeil;
}

struct TaskReceipt {
    uint256 id;
    Task[] tasks;
    uint256 expiryDate;
}

contract CalldataFeatureErrors {
    mapping(uint256 => bytes32) public taskReceiptHash;

    function submitTaskReceipt(TaskReceipt calldata _taskReceipt) external {
        _storeTaskReceipt(_taskReceipt);
    }

    function _storeTaskReceipt(TaskReceipt memory _taskReceipt) private {
        bytes32 hashedTaskReceipt = keccak256(abi.encode(_taskReceipt));
        taskReceiptHash[_taskReceipt.id] = hashedTaskReceipt;
    }
}

Gotcha: it works for me, when you set the private fn data location to calldata (which wasnt possible before 0.6.9).

function _storeTaskReceipt(TaskReceipt calldata _taskReceipt) private

Unfortunately I need it to be memory however because sometimes I want to pass a memory variable here.

We are battling with this as well. We've implemented a workaround by removing all references types in our Structs which get copied over from calldata to memory.

For anyone looking for the actual assertion:

Unimplemented feature:
/Users/alex/Projects/solidity/libsolidity/codegen/CompilerUtils.cpp(1109): Throw in function void solidity::frontend::CompilerUtils::convertType(const solidity::frontend::Type &, const solidity::frontend::Type &, bool, bool, bool)
Dynamic exception type: boost::wrapexcept<solidity::langutil::UnimplementedFeatureError>
std::exception::what: 
[solidity::util::tag_comment*] = 

And https://github.com/ethereum/solidity/issues/9037#issuecomment-635269815 reported the same problem.

Hi - I thought I'd check back in. I am using solidity 0.7.4 and still get the same bug. Are there plans to fix this for the 0.7 series or will it take longer ?

Does this mean this will be fixed in the next solidity version?

@gitpusha Yes, it should be fixed in next solidity version

@gitpusha can you check with remix set to nightly? Are there any other calldata related things you found not working?

I am re-opening this issue until we merge PRs that fix both nested structs and arrays

@gitpusha can you check with remix set to nightly? Are there any other calldata related things you found not working?

Unfortunately I was not able to reproduce all the Unimplemented Feature errors I am getting in my codebase that I compile with 0.7.4 as it's cumbersome to copy it all over to Remix. I tried to distill it but the bugs then disappaered. Unfortunately I do not have more time atm to try to distill further.

So I cannot say whether the new nightly 0.7.6 commit has indeed removed all the UnimplementedFeature Errors related to passing nested structs as parameters to internal function calls.

However, I did find some errors using:
image

I still get an Internal Compiler Error for calldata and using for library methods. I already reported this issue here: https://github.com/ethereum/solidity/issues/10186

image

When I remove that using for calldata dependency I also still get UnimplementedFeatureError

image

However, the reasons might not be the aforementioned passing of calldata structs between functions. Here is some distilled code to reproduce the Internal Compiler Error and a new Unimplemented Feature error I found which seems to be related to copy calldata nested struct arrays into a memory nested struct field during assignment.

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.5;
pragma experimental ABIEncoderV2;

struct Condition {
    address addr;
    bytes data;
}

struct Action {
    address addr;
    bytes data;
    bool viaService;
    bool scanReturndata;
}

struct Task {
    uint256 expiry;
    Condition[] conditions; // optional
    Action[] actions;
}

struct TaskReceipt {
    uint256 id;
    address client;
    address service;
    Task[] tasks;
    uint256 index;
    uint256 cycleId;
    uint256 iterations;
}

struct NoDataAction {
    address addr;
    bool viaService;
    bool scanReturndata;
}

library GelatoTaskReceipt {
    function task(TaskReceipt calldata _taskReceipt)
        internal
        pure
        returns (Task memory)
    {
        return _taskReceipt.tasks[_taskReceipt.index];
    }
}

abstract contract BService {
    using GelatoTaskReceipt for TaskReceipt;

    function assign(Task[] calldata _tasks, uint256 _iterations)
        public
        payable
        virtual
        returns (TaskReceipt memory taskReceipt)
    {
        taskReceipt = this.store(
            address(this), 
            _tasks,
            0, // start index
            _iterations == 1 ? false : true, 
            0, 
            _iterations
        );
    }

    function store(
        address _client,
        Task[] calldata _tasks,
        uint256 _index,
        bool _newCycle,
        uint256 _cycleId,
        uint256 _iterations
    ) public virtual returns (TaskReceipt memory taskReceipt) {
        uint256 nextTaskReceiptId = 1;

        // @dev Uncomment this for debugging
        // taskReceipt = TaskReceipt({
        //     id: nextTaskReceiptId,
        //     client: _client,
        //     service: address(0),
        //     tasks: _tasks,
        //     index: _index,
        //     cycleId: _newCycle ? nextTaskReceiptId : _cycleId,
        //     iterations: _iterations
        // });
    }

    function canExec(TaskReceipt calldata _taskReceipt)
        public
        view
        virtual
        returns (string memory res, bytes[] memory returndatas)
    {
        // @dev Uncomment this for debugging    
        // if (
        //     _taskReceipt.task().expiry != 0 &&
        //     _taskReceipt.task().expiry <= block.timestamp // solhint-disable-line not-rely-on-time
        // ) res = "TaskExpired";
    }
}

contract ServicePreCondition is BService {
    using GelatoTaskReceipt for TaskReceipt;

    function canExec(TaskReceipt calldata _taskReceipt)
        public
        view
        virtual
        override
        returns (string memory res, bytes[] memory returndatas)
    {
        (res, ) = super.canExec(_taskReceipt);
        returndatas = new bytes[](0);

    }
}

@mijovic do you know if #10385 also fixed the umimplemented feature issue that is thrown when you uncomment what's in the body of the store function I pased in the above ?

@gitpusha I just checked on latest develop, and I can confirm it compiles now, even after uncommenting body of store function.

Great thanks! Looking forward to the new release!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ddeclerck picture ddeclerck  路  3Comments

leviadam picture leviadam  路  4Comments

axic picture axic  路  3Comments

chriseth picture chriseth  路  3Comments

axic picture axic  路  4Comments