185 lines
4.5 KiB
Solidity
185 lines
4.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
// heavily inspired by: https://bitbucket.org/rhitchens2/soliditystoragepatterns/src/master/OneToMany.sol
|
|
pragma solidity ^0.8.8;
|
|
|
|
import "./EnumerableSetExtensions.sol";
|
|
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
|
|
library Mappings {
|
|
|
|
using EnumerableSet for EnumerableSet.Bytes32Set;
|
|
using EnumerableSetExtensions for EnumerableSetExtensions.ClearableBytes32Set;
|
|
|
|
type KeyId is bytes32;
|
|
type ValueId is bytes32;
|
|
|
|
struct Mapping {
|
|
EnumerableSet.Bytes32Set _keyIds;
|
|
EnumerableSet.Bytes32Set _valueIds;
|
|
mapping(KeyId => Key) _keys;
|
|
mapping(ValueId => Value) _values;
|
|
}
|
|
struct Key {
|
|
EnumerableSetExtensions.ClearableBytes32Set _values;
|
|
}
|
|
|
|
struct Value {
|
|
KeyId _keyId;
|
|
}
|
|
|
|
function exists(Mapping storage map, KeyId key)
|
|
internal
|
|
view
|
|
returns (bool)
|
|
{
|
|
return map._keyIds.contains(KeyId.unwrap(key));
|
|
}
|
|
|
|
function exists(Mapping storage map, KeyId key, ValueId value)
|
|
internal
|
|
view
|
|
returns (bool)
|
|
{
|
|
bytes32 val = ValueId.unwrap(value);
|
|
return map._keys[key]._values.contains(val) &&
|
|
map._valueIds.contains(val);
|
|
}
|
|
|
|
function keys(Mapping storage map)
|
|
internal
|
|
view
|
|
returns(KeyId[] memory)
|
|
{
|
|
return _toKeyIds(map._keyIds.values());
|
|
}
|
|
|
|
function values(Mapping storage map,
|
|
KeyId key)
|
|
internal
|
|
view
|
|
returns(ValueId[] memory)
|
|
{
|
|
require(exists(map, key), "key does not exist");
|
|
return _toValueIds(map._keys[key]._values.values());
|
|
}
|
|
|
|
function count(Mapping storage map,
|
|
KeyId key)
|
|
internal
|
|
view
|
|
returns(uint256)
|
|
{
|
|
require(exists(map, key), "key does not exist");
|
|
return map._keys[key]._values.length();
|
|
}
|
|
|
|
function count(Mapping storage map)
|
|
internal
|
|
view
|
|
returns(uint256)
|
|
{
|
|
return map._valueIds.length();
|
|
}
|
|
|
|
function insertKey(Mapping storage map, KeyId key)
|
|
internal
|
|
returns (bool)
|
|
{
|
|
require(!exists(map, key), "key already exists");
|
|
return map._keyIds.add(KeyId.unwrap(key));
|
|
// NOTE: map._keys[key]._values contains a default EnumerableSet.Bytes32Set
|
|
}
|
|
|
|
function insertValue(Mapping storage map, KeyId key, ValueId value)
|
|
internal
|
|
returns (bool success)
|
|
{
|
|
require(exists(map, key), "key does not exists");
|
|
require(!exists(map, key, value), "value already exists");
|
|
success = map._valueIds.add(ValueId.unwrap(value));
|
|
assert (success); // value addition failure
|
|
map._values[value]._keyId = key;
|
|
|
|
success = map._keys[key]._values.add(ValueId.unwrap(value));
|
|
}
|
|
|
|
function insert(Mapping storage map, KeyId key, ValueId value)
|
|
internal
|
|
returns (bool success)
|
|
{
|
|
if (!exists(map, key)) {
|
|
success = insertKey(map, key);
|
|
assert (success); // key insertion failure
|
|
}
|
|
if (!exists(map, key, value)) {
|
|
success = insertValue(map, key, value);
|
|
}
|
|
}
|
|
|
|
function deleteKey(Mapping storage map, KeyId key)
|
|
internal
|
|
returns (bool success)
|
|
{
|
|
require(exists(map, key), "key does not exist");
|
|
require(count(map, key) == 0, "references values");
|
|
success = map._keyIds.remove(KeyId.unwrap(key)); // Note that this will fail automatically if the key doesn't exist
|
|
assert(success); // key removal failure
|
|
delete map._keys[key];
|
|
}
|
|
|
|
function deleteValue(Mapping storage map, KeyId key, ValueId value)
|
|
internal
|
|
returns (bool success)
|
|
{
|
|
require(exists(map, key), "key does not exist");
|
|
require(exists(map, key, value), "value does not exist");
|
|
|
|
success = map._valueIds.remove(ValueId.unwrap(value));
|
|
assert (success); // value removal failure
|
|
delete map._values[value];
|
|
|
|
success = map._keys[key]._values.remove(ValueId.unwrap(value));
|
|
|
|
}
|
|
|
|
function clear(Mapping storage map, KeyId key)
|
|
internal
|
|
returns (bool success)
|
|
{
|
|
require(exists(map, key), "key does not exist");
|
|
|
|
map._keys[key]._values.clear();
|
|
success = deleteKey(map, key);
|
|
}
|
|
|
|
function _toKeyIds(bytes32[] memory array)
|
|
private
|
|
pure
|
|
returns (KeyId[] memory result)
|
|
{
|
|
// solhint-disable-next-line no-inline-assembly
|
|
assembly {
|
|
result := array
|
|
}
|
|
}
|
|
|
|
function _toValueIds(bytes32[] memory array)
|
|
private
|
|
pure
|
|
returns (ValueId[] memory result)
|
|
{
|
|
// solhint-disable-next-line no-inline-assembly
|
|
assembly {
|
|
result := array
|
|
}
|
|
}
|
|
|
|
function toKeyId(ValueId valueId) internal pure returns (KeyId) {
|
|
return KeyId.wrap(ValueId.unwrap(valueId));
|
|
}
|
|
|
|
function toKeyId(address addr) internal pure returns (KeyId) {
|
|
return KeyId.wrap(bytes32(uint(uint160(addr))));
|
|
}
|
|
}
|