3

I'm having a huge dilemma! I'm developping a smart contract that has, let's say Struct A and Struct B. Because I want to have a function retrieving all objects type A and B, I created a Struct All that is as follows:

struct All {
   A[] a;
   B[] b;
}
struct A {...}
struct B {...}

This way, I could create objects of both types and append them to the [] of each.. in the end, I would return the All in case I want to get all a and b or All.a or All.b if convenient. So far so good. Although, now when it comes to deleting and editing it is much harder and I have to iterate through all the arrays and delete or change the one I want, and shift or something else needed. So I thought, ok, maps are easier to do this. But on the other and, with a map, I can not just retrieve the full content of both structs.

I know this ain't a straight question, rather a looking for help, hoping for someone who has crossed the same situation or knows how to handle this type of contract designing dilemma.

Basically, I would like to be able to retrieve all the different types of structs I may have, but also want to be able to edit the pool where they sit. Is there any suggestion?

  • 2
    Is the order of elements in each array important? If not, then a combination of an array and a mapping would be useful. You might want to check this answer for an example of how it can be achieved. – goodvibration Feb 6 at 15:56
  • I checked this post before ethereum.stackexchange.com/a/1528/51252 and I made use of its purpose. By using it, the deleting process is ok, assuming the element's order is not relevant (even though all this for loops are a bit scary). My biggest problem is regarding the editing part. – João Quintanilha Feb 6 at 15:59
  • Ok, I just took a quick look at your answer and seemed like it is what I'm looking for. Thank you – João Quintanilha Feb 6 at 16:06
3

It's a bit of a dilemma. You need mappings for random access and arrays for iteration. You should aim to avoid loops at all costs, for O(1) complexity so the contract scales.

This library helps with CRUD ops on sets, including delete. There is a quick explainer-style walk-though over here, with links back to the detailed tutorial that explains how it works and why it should work that way. The latter might be the best place to start as it focuses on precisely the delemma you are looking at and how to solve it. The library implementation (more recent) is a code update and refinement of the principle.

pragma solidity 0.5.16;

import "./HitchensUnorderedKeySet.sol";

contract AB {

    using HitchensUnorderedKeySetLib for HitchensUnorderedKeySetLib.Set;

    struct A {
        uint arg1;
        uint arg2;
    }

    struct B {
        uint arg1;
        uint arg2;
    }

    struct All {
        HitchensUnorderedKeySetLib.Set aSet;
        HitchensUnorderedKeySetLib.Set bSet;
        mapping(bytes32 => A) aStructs;
        mapping(bytes32 => B) bStructs;
    }

    HitchensUnorderedKeySetLib.Set allSet;
    mapping(bytes32 => All) allStructs;

^^^ That is your layout.

    // Now, CRUD ops

    function newAll(bytes32 key) public {
        allSet.insert(key);
    }

    function delAll(bytes32 key) public {
        All storage a = allStructs[key];
        require(a.aSet.count() == 0, "Remove A elements first."); // optional cleanup requirements
        require(a.bSet.count() == 0, "Remove B elements first.");
        delete allStructs[key];
        allSet.remove(key);
    }

    function appendASet(bytes32 allKey, bytes32 aKey, uint arg1, uint arg2) public {
        All storage a = allStructs[allKey];
        require(allSet.exists(allKey), "allKey does not exist."); 
        a.aSet.insert(aKey);
        A storage aa = a.aStructs[aKey];
        aa.arg1 = arg1;
        aa.arg2 = arg2;
    }

    // carry on with appendB, delete A and delete B.
}

You may (should) also implement count, exists, and keyAtIndex for All, A and B for a complete CRUD implementation of the whole structure. count() and keyAtIndex() functions let a client iterate over the lists one element at a time, which is ideal because the operations complete with fixed gas cost at any scale (a client can iterate so the contract doesn't have to).

In a nested structs situation, it might be nice to expose some count info:

function getAllStruct(bytes32 key) public returns (uint countA, uint countB) {
  All storage a = allStructs[key];
  return(a.aSet.count(), a.bSet.count(), members ... );
}
| improve this answer | |
  • This is extremely helpful, thank you, sir. – João Quintanilha Feb 6 at 16:48
  • My pleasure. Thanks for accepting my answer. – Rob Hitchens Feb 6 at 18:33
4

For those who actually find themselves in the same situation as I did, take a look at this answer, it answers my dilemma. Credits to @goodvibration

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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