Solidity – How to Return an Array of Structs Using Events?

arrayscontract-designeventsstoragestruct

I wan’t my function to spit out an array of structs. I know this is not straightforwardly possible. What is the best way for me to proceed? Should I loop through my array in my function and trigger an event each time to return the arguments of each struct separately? Should I create a large json object by looping through my array and spit it out as a string? Is there a more straightforward way of doing this?

Best Answer

I'm going to suggest a more ethereum-like approach.

You can probably do that with the experimental ABI encoder. As well as stability concerns, there is a serious problem with overall approach. The gas cost of the operation will increase with the size of the array. That will make it impractical or impossible to use if the array grows too large.

In most cases, returning the whole array is wasteful and redundant. You can, and should, emit events for each state change. This means observers, clients and servers, can know the state of the array without even asking. It mitigates the need to return the whole array in most cases.

There are cases where a client or a contract would want to know the contents of an element. An element can be returned in a function with O(1) complexity and a consistent gas cost at any scale, so that's what we should do.

Clients may wish to iterate (for whatever reason). However, the chore of iterating can usually be externalized. Let the client call the O(1) function as many times as necessary. Remember, the blockchain is the source of truth and the evidence of data integrity. It is not a general-purpose computing platform and processing is costly.

The stable ABI can pass structs in/out of functions internally but not externally. Externally, we break the struct down into members.

Storage organization is a challenge, so this might be helpful: Are there well-solved and simple storage patterns for Solidity?

Going with the simplest array idea, I came up with this little example that expresses the suggested approach.

pragma solidity 0.5.1;

contract ArrayOfStructs {

    // just an arbitrary struct layout

    struct Thing {
        uint a;
        bool b;
    }

    // an array of structs

    Thing[] public things; 

    event LogAppendThing(address sender, uint a, bool b);

    // set

    function append(uint _a, bool _b) public {
        Thing memory t = Thing({
            a: _a,
            b: _b
        });
        things.push(t);

        // emit an event

        emit LogAppendThing(msg.sender, _a, _b);
    }

    // "public" in line 14 gives a "free" getter, like

    /*
    function things(uint index) public view returns(uint, bool) {
        return (things[index].a, things[index].b);
    }
    */

    // it can be helpful to give a client a count, so it can know the boundary.

    function thingCount() public view returns(uint count) {
        return things.length;
    }
}

Hope it helps.