0

I'm trying to create a map whose key is composed of two bytes32 keys.

I've tried with tuples-keys and struct-keys but both are rejected by the compiler.

I'm also thinking about "hashing" both keys but not sure how to do it.

Any code reference around?

Update

"Digging around" It looks to me that a function like bytes32 hashOfMultiKey = keccak256(abi.encodePacked(i1,i2)); will be "enough" for most purposes, but I'm losing info about the original keys. I'm wondering whether there will be a simpler alternative that keeps all the original info about the input multi-keys.

  • "Digging arount" It looks to me that a function like bytes32 hashOfMultiKey = keccak256(abi.encodePacked(i1,i2)); will be "enough" for most purposes, but I'm loosing info about the original keys. I'm wondering whether there will be a simpler alternative that keeps all the original info about the input multi-keys. – earizon Apr 16 at 8:09
  • Could you explain what you mean by "losing info about the original keys"? You could always do mapping(bytes32 => mapping(bytes32 => x)) foo, but this ultimately does pretty much the same thing as your keccak256(abi.encodePacked(i, i2)) scheme. (Mappings are implemented by concatenation and hashing to determine where data will be stored/retrieved.) – smarx Apr 16 at 18:08
0

There are two basic approaches and I can talk about a third approach that is useful in some cases.

Hash doesn't have to be lossy.

event LogSet(bytes32 keyA, bytes32 keyB, bytes32 value);

function set(bytes32 keyA, bytes32 keyB, bytes32 value) public {
  myMap(multikey(keyA, keyB) = value;
  emit Log(keyA, keyB, value);
}

function multikey(bytes32 keyA, bytes32 keyB) public pure returns(bytes32) {
  return keccak256(abi.encodedPacked(keyA, keyB));
}

The event log ensure observers have a way to discover all A/B combinations that have ever been used, so it's perfectly reasonable to ask for them in a get() function. It's not lossy, but the contract itself wouldn't be able to enumerate them. It would rely on external inputs and then perform the multikey computation.

Mapping of Mapping

Perhaps counter-intuitively, there is no storage cost increase to store a mapping inside a mapping.

// keyA => mapping(keyB => value)
mapping(bytes32 = mapping(bytes32 => bytes32));

What does the contract need to enumerate?

Clients usually have various needs such as "get all B in A", order by "whatever", etc. What the contract needs for its own internal logic is usually much less. Some thoughts on this topic: https://blog.b9lab.com/the-joy-of-minimalism-in-smart-contract-design-2303010c8b09

It is not uncommon that a contract has a hard need to check for existance of keyB in set keyA, or keyA in set keyB. You may be able to get away with check values != bytes32(0) or you may need something slightly more elaborate. Pay particular attention to the need, or lack of need to remove an item from the set. Once a member, always a member? Or, it is conceivable for a member to leave the set?

Have a look at this library for a way to treat all of your keys as members of sets with a remove capability. This would enable support of "is A in B?" and "is B in A?" and the ability to logically remove members.

https://medium.com/robhitchens/solidity-crud-epilogue-e563e794fde

Github: https://github.com/rob-Hitchens/UnorderedKeySet

pragma solidity 0.5.1;

import "./HitchensUnorderedKeySet.sol";

contract KeyAKeyB {

    using HitchensUnorderedKeySetLib for HitchensUnorderedKeySetLib.Set;

    // Key B in A
    mapping(bytes32 => HitchensUnorderedKeySetLib.Set) bInA;

    // Key A in B
    mapping(bytes32 => HitchensUnorderedKeySetLib.Set) aInB;

    // multikey => value;
    mapping(bytes32 => bytes32) public values;

    event LogSet(bytes32 keyA, bytes32 keyB, bytes32 value);
    event LogRem(bytes32 keyA, bytes32 keyB);

    function multikey(bytes32 keyA, bytes32 keyB) public pure returns(bytes32) {
        return keccak256(abi.encodePacked(keyA, keyB));
    }

    // When inserting A, B, value, use a compound key to store the value, and maintain lists of B in A and A in B.
    // Below follows an update or overwrite pattern. 

    function set(bytes32 keyA, bytes32 keyB, bytes32 value) public {
        if(!isBInA(keyA, keyB)) bInA[keyA].insert(keyB); 
        if(!isAInB(keyA, keyB)) aInB[keyB].insert(keyB); 
        values[multikey(keyA, keyB)] = value;
        emit LogSet(keyA, keyB, value);
    }

    function get(bytes32 keyA, bytes32 keyB) public view returns(bytes32 value) {
        // optionally, check B exists in A to differentiate exists but 0x0 from not stored.
        // require(bInA[keyA].exists(keyB));
        return values[multikey(keyA, keyB)];
    }

    // This remove function will reject removing a key pair that does not exist

    function remove(bytes32 keyA, bytes32 keyB) public {
        bInA[keyA].remove(keyB);
        aInB[keyB].remove(keyA);
        delete values[multikey(keyA, keyB)];
        emit LogRem(keyA, keyB);
    }

    // Check existance

    function isBInA(bytes32 keyA, bytes32 keyB) public view returns(bool isIndeed) {
        return bInA[keyA].exists(keyB);
    }

    function isAInB(bytes32 keyA, bytes32 keyB) public view returns(bool isIndeed) {
        return aInB[keyB].exists(keyA);
    }

    // So clients can enumerate keys, all B in A, and all A in B

    function countBinA(bytes32 keyA) public view returns(uint) {
        return bInA[keyA].count();
    }

    function countAinB(bytes32 keyB) public view returns(uint) {
        return aInB[keyB].count();
    }

    function aBAtIndex(bytes32 keyA, uint row) public view returns(bytes32 keyB) {
        return bInA[keyA].keyAtIndex(row);
    }

    function bAAtIndex(bytes32 keyB, uint row) public view returns(bytes32 keyA) {
        return aInB[keyB].keyAtIndex(row);
    }
}

This example is probably more ambitious than actually required. Hopefully it shows how a variety of concerns can be addressed while observing the ideals of strict enforcement of relational integrity and fully discoverable internal structure.

Hope it helps.

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.