5

I have a contract that contains a simple mapping; let's say mapping(address => uint256) balances;

I want to be able to somehow get all the participants of this smart contract. The simplest way seems to have an additional dynamic array of addresses and iterate over them. But searching for an address on each token transfer or payable function call looks overcomplicated.

Is there a way to have something like a set() in Python, i.e. an array of unique addresses?

4 Answers 4

5

There's no native set in solidity but you can work something simpler with an array and a mapping.

It is very simple, but have the disadvantage it is append only set, you cannot easily delete an element from it.

contract SimpleSet {
    // Mapping from address to position in the array
    // 0 means the address is not in the array
    mapping (address => uint) index;

    // Array with address 
    address[] store;

    function SimpleSet() public {
        // We will use position 0 to flag invalid address
        store.push(0x0);
    }

    function addToArray(address who) public {
        if (!inArray(who)) {
            // Append
            index[who] = store.length;
            store.push(who);
        }
    }

    function inArray(address who) public view returns (bool) {
        // address 0x0 is not valid if pos is 0 is not in the array
        if (who != 0x0 && index[who] > 0) {
            return true;
        }
        return false;
    }

    function getPosition(uint pos) public view returns (address) {
        // Position 0 is not valid
        require(pos > 0); 
        return store[pos];
    }
}
4
  • Another HUGE problem of this is that double memory is used -> each address is stored in the index mapping and in store array. I have posted a way to improve this. Dec 14, 2019 at 19:51
  • 2
    @RahulKothari You are incorrect in that addresses are stored in the index mapping. Mappings do not store the key, only the value is stored.
    – Ismael
    Dec 15, 2019 at 19:08
  • if mappings aren't stored, how does the map know what keys have a value? I will appreciate it if you can send a link to the docs detailing this! Dec 17, 2019 at 12:06
  • 1
    @RahulKothari There are several articles about that you can start with medium.com/@hayeah/…, the official documentation solidity.readthedocs.io/en/v0.5.15/…, and this programtheblockchain.com/posts/2018/03/09/…
    – Ismael
    Dec 18, 2019 at 1:59
1

In your payable function, add the sender to an array if she's not there already. Given that you have a balances mapping, you can check that for free:

mapping(address => uint) balances;
address[] participants;

function deposit() payable {
  if (balances[msg.sender] == 0) {
    participants.push(msg.sender);
  }
  balances[msg.sender] += msg.value;
}

For your token transfer function, you might want to remove participants when their balance goes back to zero. In that case you need an extra mapping to store the participants' indices when you add them:

mapping(address => uint) balances;
address[] participants;
mapping(address => uint) indices;

function transfer(address recipient, uint amount) {
  if (balances[recipient] == 0) {
    participants.push(recipient);
    indices[recipient] = participants.length - 1;
  }
  // insert transfer logic here
  if (balances[msg.sender] == 0) {
    delete participants[indices[msg.sender]];
  }
}

If you have more than one of these functions, consider using modifiers.

0

No, there are no sets in solidity. You will need to add each member only once to this mapping, and then implement a counter to increment/decrement the length of the mapping every time a member joins or leaves.

Loops are very frowned upon in solidity and I would avoid as much as you can. You will eventually hit the gas limit and break your contract when the length gets too large.

1
  • 1
    Given that it's a mapping, members are added only once already. The problem is, you can't retrieve its keys so it's impossible to derive an array of unique addresses from it. That's why you need more than a counter: you need an actual array. As for loops, I don't think they are "very frowned upon"; like in every other programming language, they add a layer of complexity but are often unavoidable.
    – e18r
    Mar 24, 2018 at 15:04
-1

All answers are helpful but can be improved upon in context of space: In both Ismael and Alex's answers, each address is stored three times - in the mapping containing the indices, in the balances mapping and in the array containing all addresses. A better way is:

Since the contract already has a mapping of balances (presumably for an ERC20), modify it to store a mapping of a struct:

Struct account {
    uint balance;
    bool doesItExistInArray
}
mapping (address => account) public balances

And then in your transfer or transferFrom function or anywhere you update balance presumably an internal function called _transfer:

function _transfer(address _from, address _to, uint value) internal {
    //do some error checking and balance checking.
    ....
    //updating balance:
    balances[_from].balance -= value;
    balances[_to].balance += value;
    if(!balances[_from].doesItExistInArray) {
        allAccounts.push(_from);
        balances[_from].doesItExistInArray = true;
    }
    //same for _to address
}

Haven't tested the code so may not be syntactically correct, but you get the logic. Here each address is only stored twice!

1
  • 1
    Your solution has the same problem you are trying to solve. Structs are padded to 32 bytes on storage. Adding a single bool will duplicate the storage used. But you can use a smaller uint248 and together with bool it should have the same size.
    – Ismael
    Dec 15, 2019 at 19:12

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.