Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of an address Enumerable Set #2061

Merged
merged 27 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b74bb85
Drafted Enumerable.sol.
alcueca Jan 15, 2020
e3227bf
Drafted test framework.
alcueca Jan 15, 2020
3a02d0d
Tweaked the tests to follow oz structure.
alcueca Jan 16, 2020
429cb6d
Coded EnumerableSet.
alcueca Jan 20, 2020
ef381e6
Moved EnumerableSet to `utils`.
alcueca Jan 20, 2020
bf335ca
Fixed linting.
alcueca Jan 20, 2020
580bbdd
Improved comments.
alcueca Jan 21, 2020
3700a34
Tweaked contract description.
alcueca Jan 21, 2020
8379d53
Merge branch 'master' into fix/enumerable-#1240
alcueca Jan 21, 2020
8b50c02
Renamed struct to AddressSet.
alcueca Jan 22, 2020
71d1716
Relaxed version pragma to 0.5.0
alcueca Jan 23, 2020
dfdf794
Removed events.
alcueca Jan 23, 2020
41cb935
Revert on useless operations.
alcueca Jan 23, 2020
31e9a54
Small comment.
alcueca Jan 23, 2020
79b98a4
Created AddressSet factory method.
alcueca Jan 23, 2020
be49c33
Failed transactions return false.
alcueca Jan 23, 2020
b770d89
Transactions now return false on failure.
alcueca Jan 23, 2020
332636d
Remove comments from mock
nventuro Jan 23, 2020
07c99bc
Rename mock functions
nventuro Jan 23, 2020
35e2af5
Adapt tests to code style, use test-helpers
nventuro Jan 23, 2020
305d714
Fix bug in remove, improve tests.
nventuro Jan 24, 2020
6742bcd
Add changelog entry
nventuro Jan 24, 2020
bc1e5b2
Add entry on Utils doc
nventuro Jan 24, 2020
81e1bf5
Add optimization for removal of last slot
nventuro Jan 24, 2020
40f6dbd
Merge branch 'master' into fix/enumerable-#1240
nventuro Jan 24, 2020
4ddcacd
Update docs
nventuro Jan 24, 2020
6a14ec4
Fix headings of utilities documentation
nventuro Jan 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions contracts/mocks/EnumerableSetMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
pragma solidity ^0.5.10;
import "../utils/EnumerableSet.sol";


/**
* @title EnumerableSetMock
* @dev Data structure - https://en.wikipedia.org/wiki/Set_(abstract_data_type)
* @author Alberto Cuesta Cañada
*/
contract EnumerableSetMock{

using EnumerableSet for EnumerableSet.AddressSet;

EnumerableSet.AddressSet private set;

constructor() public {
set = EnumerableSet.AddressSet({values: new address[](0)});
alcueca marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @dev Returns true if the value is in the set.
*/
function testContains(address value)
public
view
returns (bool)
{
return EnumerableSet.contains(set, value);
}

/**
* @dev Insert an value as the new tail.
*/
function testAdd(address value)
public
{
EnumerableSet.add(set, value);
}

/**
* @dev Remove an value.
*/
function testRemove(address remove)
public
{
EnumerableSet.remove(set, remove);
}

/**
* @dev Return an array with all values in the set, from Head to Tail.
*/
function testEnumerate()
public
view
returns (address[] memory)
{
return EnumerableSet.enumerate(set);
}
}
84 changes: 84 additions & 0 deletions contracts/utils/EnumerableSet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
pragma solidity ^0.5.10;
alcueca marked this conversation as resolved.
Show resolved Hide resolved


/**
* @title EnumerableSet
* @dev Data structure - https://en.wikipedia.org/wiki/Set_(abstract_data_type)
*
* An EnumerableSet.AddressSet is a data structure containing a number of unique addresses.
*
* - It is possible to add and remove addresses in O(1).
* - It is also possible to query if the AddressSet contains an address in O(1).
* - It is possible to retrieve an array with all the addresses in the AddressSet using enumerate.
* This operation is O(N) where N is the number of addresses in the AddressSet. The order in
* which the addresses are retrieved is not guaranteed.
*
* Initialization of a set must include an empty array:
* `EnumerableSet.AddressSet set = EnumerableSet.AddressSet({values: new address[](0)});`
*
* @author Alberto Cuesta Cañada
*/
library EnumerableSet {

event ValueAdded(address value);
alcueca marked this conversation as resolved.
Show resolved Hide resolved
event ValueRemoved(address value);

struct AddressSet {
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping (address => uint256) index;
address[] values;
}

/**
* @dev Add a value. O(1).
*/
function add(AddressSet storage set, address value)
internal
{
if (!contains(set, value)){
alcueca marked this conversation as resolved.
Show resolved Hide resolved
set.index[value] = set.values.push(value);
emit ValueAdded(value);
}
}

/**
* @dev Remove a value. O(1).
*/
function remove(AddressSet storage set, address value)
internal
{
if (contains(set, value)) {
alcueca marked this conversation as resolved.
Show resolved Hide resolved
set.values[set.index[value] - 1] = set.values[set.values.length - 1];
nventuro marked this conversation as resolved.
Show resolved Hide resolved
set.values.pop();
delete set.index[value];
nventuro marked this conversation as resolved.
Show resolved Hide resolved
emit ValueRemoved(value);
}
}

/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value)
internal
view
returns (bool)
{
return set.index[value] != 0;
}

/**
* @dev Return an array with all values in the set. O(N).
*/
function enumerate(AddressSet storage set)
internal
view
returns (address[] memory)
{
address[] memory output = new address[](set.values.length);
for (uint256 i; i < set.values.length; i++){
output[i] = set.values[i];
}
return output;
}
}
54 changes: 54 additions & 0 deletions test/utils/EnumerableSet.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const { contract } = require('@openzeppelin/test-environment');

const { expect } = require('chai');

const EnumerableSetMock = contract.fromArtifact('EnumerableSetMock');

const a = '0x0000000000000000000000000000000000000001';
const b = '0x0000000000000000000000000000000000000002';
const c = '0x0000000000000000000000000000000000000003';

/** @test {EnumerableSet} contract */
describe('EnumerableSet', function () {
beforeEach(async function () {
this.set = await EnumerableSetMock.new();
});

it('contains can return false.', async function () {
expect(await this.set.testContains(a)).to.equal(false);
});

it('adds a value.', async function () {
await this.set.testAdd(a);
expect(await this.set.testContains(a)).to.equal(true);
});

it('adds several values.', async function () {
alcueca marked this conversation as resolved.
Show resolved Hide resolved
await this.set.testAdd(a);
await this.set.testAdd(b);
expect(await this.set.testContains(a)).to.equal(true);
expect(await this.set.testContains(b)).to.equal(true);
expect(await this.set.testContains(c)).to.equal(false);
});

it('removes values.', async function () {
await this.set.testAdd(a);
alcueca marked this conversation as resolved.
Show resolved Hide resolved
await this.set.testRemove(a);
expect(await this.set.testContains(a)).to.equal(false);
});

it('enumerates values as an empty array', async function () {
expect(await this.set.testEnumerate()).to.eql([]);
});

it('enumerates an array of values', async function () {
await this.set.testAdd(a);
await this.set.testAdd(b);
await this.set.testAdd(c);
const result = (await this.set.testEnumerate());
expect(result.length).to.be.equal(3);
expect(result[0]).to.be.equal(a);
expect(result[1]).to.be.equal(b);
expect(result[2]).to.be.equal(c);
});
});