0

I have an array of addresses and a function which returns how many times an address is in the array. For example, if the array has ['0x00', '0x01', '0x00'] the function will return 2 for 0x00 and 1 for 0x01

I would like to know if my approach is efficient enough to work with a large number of addresses

address[] public users;
function userCounter(address user) public view returns(uint){
        uint counter;
        for(uint i; i<users.length; i++){
            if(users[i] == user){
                counter++;
            }
        }
        return counter;
    } 

Any idea how I could improve it? Currently it takes a while to iterate over 300 elements so I imagine how bad it's going to be for a larger number.

  • I'd suggest a better approach. How do they get into the array and what is the purpose of counting them? Just a contrived example, or deeper purpose? – Rob Hitchens - B9lab Sep 27 '18 at 20:41
  • @RobHitchens They get into the array by calling a payable function and sending 0.0002 ETH. The function uses users.push(msg.sender). The purpose of counting them is to allow them to check how many entries they have so far. Maybe how many entries other colleagues have as well. – Cristian Sep 27 '18 at 20:48
  • It depends on where are you intending to use this function. If it's about to be called from app/server exclusively, than this is safe to use. But keep in mind that if some other, non view function, from this contract, or any other, calls this one, it will consume gas and you might end up blocking your contract once length of array grows enough. Since you need to count times balance has been updated, I suggest using two mappings, or struct with balance and count. – KwahuNashoba Sep 27 '18 at 20:54
  • Thanks @KwahuNashoba ! I will have no other function calling this one, just the users on the server side. I will also consider mappings or struct as an implementation for this – Cristian Sep 27 '18 at 21:00
1

You might be interested in using two storage structures for your scenario.

A mapping which can hold the key + value of address + count, and an array which holds the unique list of addresses which can be iterated on.

You mention a payable function which creates the original list of users through a payment. Here is an example of how that could be written to support these types:

pragma solidity ^0.4.24;

contract example {
    address[] public users;
    mapping (address => uint) public count;

    function donate() public payable{
        require(msg.value == .002 ether);
        if (count[msg.sender] == 0) {
            users.push(msg.sender);
        }

        count[msg.sender] += 1;

    }
}

In this case, whenever a brand new user donates .002 ETH, they get added to the users array. Otherwise, we simply increment on the mapping which tracks the count of donations per user.

Then your userCounter function simply becomes a read on the count mapping:

function userCounter(address user) public view returns(uint) {
    return count[user];
}
1

The reason this less than ideal is the unbounded for loop. It will get increasingly expensive. A greater concern is that will not work at all when the cost of iterating over the loop exceeds the block gasLimit, which is a network property voted on by the miners.

It's bad form to make a contract that is sure to fail at scale.

A solution that problem is to rethink the insertion process so the number you need is available at a consistent cost at any scale.

pragma solidity 0.4.25;

contract Counter {

    struct UserStruct {
        uint balance;
        uint counter;
    }

    mapping(address => UserStruct) public userStructs;

    function deposit() public payable returns(bool success) {
        require(msg.value > 0);
        userStructs[msg.sender].balance += msg.value;
        userStructs[msg.sender].counter ++;
        return true;
    }
}

Hope it helps.

  • I have one question regarding what you mentioned. You said that the 'for' loop will get expensive. Is it not free since I am calling it with the 'view' modifier? Technically I was looping over the array's elements so I was just fetching which is free, isn't it?. Or did I miss something? – Cristian Sep 27 '18 at 21:12
  • 1
    You raise two good points for consideration. First, gas from view is returned if it's called externally, but not if it's called from a contract. In either case, gas accounting and limits still apply. More info over here: ethereum.stackexchange.com/questions/59036/… – Rob Hitchens - B9lab Sep 27 '18 at 21:36
  • Another approach to iterative processes is to rely on clients iterating over functions that return one row at a time. When you think of this as a client responsibility, you might find it's not important for the contract to track the counter at all. – Rob Hitchens - B9lab Sep 27 '18 at 21:37

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.