1
contract Contract {

struct A {
  int a;
}

struct B {
  A[] as;
}

mapping(address => B[]) bs;

function addB(B memory b) {
  bs[msg.sender].push(...???...);
}

}

I have nested hell of arrays and structs and I can't figure out how to properly add one B into array of bs.

1

nested hell

Yup.

This is circular:

struct B {
  B[] as;
}

B contains arrays of B?

The dynamic array of a struct inside a struct pushes the limits of what the compiler can handle. You would have to construct a memory instance of the struct and use the push method, which isn't present and then copy the works into storage.

As a general suggestion, instead of fighting it, use an array of keys and put the structs in a mapping you can access outside of the structs.

Roughly:

struct Inner {
  members ...
}

struct Outer {
  address[] InnerKeys;
  more members ... 
}

mapping(address => Inner) innerStructs;
mapping(address => Outer) outerStructs;

Use address, bytes32 or uint for keys, as appropriate.

You'll find that a lot easier to contend with.

You didn't say what you need to use this for. It often has to do with transient sets such as where used, membership, etc., and that can imply the need to logically remove and iterate members. You might find this approach useful: https://github.com/rob-Hitchens/UnorderedKeySet

This little scribble is making decent progress on app-level concerns instead of getting hung up on organization.

pragma solidity ^0.5.1;

import "./HitchensUnorderedAddressSet.sol";

contract NestedStructs {

    using HitchensUnorderedAddressSetLib for HitchensUnorderedAddressSetLib.Set;

    struct UserStruct {
        uint balance;
        bool active;
        HitchensUnorderedAddressSetLib.Set followingSet;
        HitchensUnorderedAddressSetLib.Set followerSet;
    }

    mapping(address => UserStruct) userStructs;
    HitchensUnorderedAddressSetLib.Set userSet;

    function createUser(address user) public { // access control is set aside for brevity
        UserStruct storage u = userStructs[user];
        userSet.insert(user); // revert on deuplicate keys
        u.active = true;
    }

    function join() public {
        createUser(msg.sender);
    }

    function followUser(address followed ) public {
        require(userSet.exists(msg.sender), "Join first please.");
        require(userSet.exists(followed), "Follow a joined user please.");
        UserStruct storage u = userStructs[msg.sender];
        UserStruct storage f = userStructs[followed];
        u.followingSet.insert(followed);
        f.followerSet.insert(msg.sender); // these revert on duplication
    }

    function unFollowUser(address unfollow) public {
        // You can add requires to create more app/human friendly error messages
        UserStruct storage u = userStructs[msg.sender];
        UserStruct storage f = userStructs[unfollow];
        u.followingSet.remove(unfollow);
        f.followerSet.remove(msg.sender); // these revert if there was no follower join to start with
    }

    function isUser(address user) public view returns(bool) {
        return userSet.exists(user);
    }

    function userCount() public view returns(uint) {
        return userSet.count();
    }

    function userInfo(address user) public view returns(uint balance, bool active, uint followingCount, uint followerCount) {
        UserStruct storage u = userStructs[user];
        balance = u.balance;
        active = u.active;
        followingCount = u.followingSet.count();
        followerCount = u.followerSet.count();
    }

    function userFollowerAtIndex(address user, uint index) public view returns(address) {
        return userStructs[user].followerSet.keyAtIndex(index);
    }

    function userFollowingAtIndex(address user, uint index) public view returns(address) {
        return userStructs[user].followingSet.keyAtIndex(index);
    }
}

There's another example using the Set library over here: Efficient Solidity storage pattern for a directional weighted graph

Hope it helps.

2
  • Sorry, that circular dependency was a mistake. Edited question. – warchantua Jan 18 '20 at 10:38
  • I don't see an obvious way to work with that data structure because the issue is the data structure. You want to be able to bs[msg.sender].push(b); but I don't think you can get away with it. – Rob Hitchens Jan 18 '20 at 11:13

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.