pragma solidity ^0.5.10; /** * @title Enumerable * @dev This contract implements an enumerable address set as a doubly linked list. * @author Alberto Cuesta CaƱada */ library Enumerables { event ObjectCreated(uint256 id, address data); event ObjectsLinked(uint256 prev, uint256 next); event ObjectRemoved(uint256 id); event NewHead(uint256 id); event NewTail(uint256 id); struct Object{ uint256 id; uint256 next; uint256 prev; address data; } struct Enumerable{ uint256 head; uint256 tail; uint256 idCounter; mapping (uint256 => Object) objects; } /** * @dev Retrieves the Object denoted by `id`. */ function get(Enumerable storage enumerable, uint256 id) public view returns (uint256, uint256, uint256, address) { Object memory object = enumerable.objects[id]; return (object.id, object.next, object.prev, object.data); } /** * @dev Insert a new Object as the new Head with `data` in the data field. */ function append(Enumerable storage enumerable, address data) public returns (bool) { uint256 objectId = _createObject(enumerable, data); if (enumerable.head == 0) { _setHead(enumerable, objectId); } else { _link(enumerable, enumerable.head, objectId); _setTail(enumerable, objectId); } } /** * @dev Remove the Object denoted by `id` from the List. */ function remove(Enumerable storage enumerable, uint256 id) public { Object memory removeObject = enumerable.objects[id]; if (enumerable.head == id && enumerable.tail == id) { _setHead(enumerable, 0); _setTail(enumerable, 0); } else if (enumerable.head == id) { _setHead(enumerable, removeObject.next); enumerable.objects[removeObject.next].prev = 0; } else if (enumerable.tail == id) { _setTail(enumerable, removeObject.prev); enumerable.objects[removeObject.prev].next = 0; } else { _link(enumerable, removeObject.prev, removeObject.next); } delete enumerable.objects[removeObject.id]; emit ObjectRemoved(id); } /** * @dev Returns true if at least one Object matches `data` in the data field. * TODO: What happens with address(0) as data? */ function contains(Enumerable storage enumerable, address data) public view returns (bool) { Object memory object = enumerable.objects[enumerable.head]; while (object.data != data) { object = enumerable.objects[object.next]; } return object.data == data; } /** * @dev Returns the length of the enumerable. */ function length(Enumerable storage enumerable) public view returns (uint256) { uint256 count = 0; if (enumerable.head != 0){ count += 1; Object memory object = enumerable.objects[enumerable.head]; while (object.id != enumerable.tail) { count += 1; object = enumerable.objects[object.next]; } } return count; } /** * @dev Returns all the data fields in the enumerable, in an array ordered from head to tail. */ function enumerate(Enumerable storage enumerable) public view returns (address[] memory) { uint256 count = length(enumerable); address[] memory data = new address[](count); Object memory object = enumerable.objects[enumerable.head]; for (uint256 i = 0; i < count; i += 1){ data[i] = enumerable.objects[object.id].data; object = enumerable.objects[object.next]; } return data; } /** * @dev Internal function to update the Head pointer. */ function _setHead(Enumerable storage enumerable, uint256 id) internal { enumerable.head = id; emit NewHead(id); } /** * @dev Internal function to update the Tail pointer. */ function _setTail(Enumerable storage enumerable, uint256 id) internal { enumerable.tail = id; emit NewTail(id); } /** * @dev Internal function to create an unlinked Object. */ function _createObject(Enumerable storage enumerable, address data) internal returns (uint256) { enumerable.idCounter += 1; uint256 newId = enumerable.idCounter; Object memory object = Object( newId, 0, 0, data ); enumerable.objects[object.id] = object; emit ObjectCreated( object.id, object.data ); return object.id; } /** * @dev Internal function to link an Object to another. */ function _link(Enumerable storage enumerable, uint256 _prevId, uint256 _nextId) internal { enumerable.objects[_prevId].next = _nextId; enumerable.objects[_nextId].prev = _prevId; emit ObjectsLinked(_prevId, _nextId); } }