1

I have a following requirement that need to be cater in dapp.

  1. Insert array of address
  2. The array need to be unique. If one of the array address already exists, reject and revert entire operation.
  3. dapp must able to retrieve the user address list
  4. dapp must able to detect if a user address exists based on a user address input

Note:

  1. The above requirement must be satisfied.
  2. Full Validation checking must be done by smart contract.(cannot rely on client side checking alone).

Trying to use 3 for loop doesn't seems be a very clever way for this as it become a cost expensive operation. Unfortunately this is the only solution i can think of to satisfy above condition. Anyone have experience on dealing with this?

// probably only one of mapping or list is needed
address[] public addressList;
mapping (address => bool) public userAddr; 


    function insertAddress(address[] addressUser) public returns (bool) {
        // loop addressUser and check if it is unique
        // loop addressUser and check if it exists in mapping
        // loop and push addressUser to addressList + insert addressUser to userAddr


        return true;
    }
2

Seeing your requirement I would do :

address[] public addressList;
mapping (address => bool) public userAddr; 

function insertAddress(address[] addressUser) public returns (bool) {
    // used to check adressUser uniqueness
    mapping (address => bool) memory uniq;
    for(uint i = 0; i < addressUser.length, i++) {
        address addr = addressUser[i];
        // check if addressUser is unique
        require(uniq[addr] == false);
        uniq[addr] = true;
        // if addr is not already list
        if (userAddr[addr] == false) {
            userAddr[addr] = true;
            addressList.push(addr);
        }
    }

    return true;
}

Edit:

After seeing another contract you could use the method here and require that all address are sent in increasing order. It is probably less costly in term of gas since less memory allocation.

That would do :

function insertAddress(address[] addressUser) public returns (bool) {
    // used to check adressUser uniqueness
    address lastAddr = addres(0);
    for(uint i = 0; i < addressUser.length, i++) {
        address addr = addressUser[i];
        // check if addressUser is unique
        // by forcing all address to be sent 
        // in increasing order
        require(addr > lastAddr);
        lastAddr = addr;
        // if addr is not already in list
        if (userAddr[addr] == false) {
            userAddr[addr] = true;
            addressList.push(addr);
        }
    }

    return true;
}
1

I have created contracts like this before and have seen many of the issues you are having. In short, these operations you are attempting to perform are expensive. Thus, the best way to do this is simply do it efficiently.

In order to satisfy the requirements, I would put the entire function in a loop that iterates over each address that is passed in. In order to make it as efficient as possible, you can use an if statement to check if the address already exists. If it does, you can simply move onto the next address without performing more calculations on the current one. Depending on how you are passing in addresses, this may have huge gas savings.

As an example, you could do:

address[] public addressList;
mapping (address => bool) public userAddr; 


    function insertAddress(address[] addressUser) public returns (bool) 
    {
        for (uint256 i = 0; i < addressUser.length; i++) {
            if (address does not exist) {
                push addressUser to addressList
                insert addressUser to userAddr
            }
        }

        return true;
    }

Edit based on the new requirements

The array need to be unique. If one of the array address already exists, reject and revert entire operation.

If this is the case, then the above does not apply and what you originally had is the best way to do it.

Edit based on comments

Thinking about it logically, in order to check the array you must check every address. This requires opcodes that check each item, and you cannot get around this. After these checks are performed, you must write each item to the blockchain, which also requires opcodes for each of these. This whole process is computationally expensive, and is the reason why a lot of this logic is generally suggested off-chain.

One thing you can do is compare the hashes of the arrays (one that you are submitting and one that is being checked). You can take the keccack256 of each array in order to ensure a unique hash. You can store this hash in the smart contract and submit a hash to be checked, rather than the entire array. By doing this, you are now only performing one check, as opposed to N checks (N being the number of items in the array).

You will still be required to loop to add all the items to the smart contract, but now you have effectively removed one loop.

An example would be:

address[] public addressList;
mapping (address => bool) public userAddr;
mapping (bytes32 => bool) public doesHashExist; 


    function insertAddress(address[] addressUser) public returns (bool) 
    {
        bytes32 newHash = keccak256(addressUser);
        require(!doesHashExist[newHash]);
        for (uint256 i = 0; i < addressUser.length; i++) {
                push addressUser to addressList
                insert addressUser to userAddr
            }
        }
        doesHashExist[newHash] = True;
        return true;
    }

You will see that there is a new mapping that is used to store hashes that exist on the contract. The function now only does a single check (as well as a hashing) to confirm existence of the array. Finally, it saves the hash.

  • 1
    The above u array is to skip and insert non unique address which i have seen it is being practiced a lot. but the requirement for the dapp is to reject entire operation even if one of it is unique. – vincentsty Nov 18 '18 at 9:03
  • 1
    Yup, i know it works, but it require at least 3 for loop (one for checking if the array pass in itself is unique, one for checking whether the array pass in have existing value, one for inserting). I am trying to look for a more cost effective way. – vincentsty Nov 18 '18 at 9:06
  • 1
    The requirement must be satisfied in the smart contract itself as per the requirement. It should not be reliant on external validation. Between, is there a way to reduce 3 for loop to 2? (eg: for loop to checking array itself is unique can be checked with other more efficient way?) – vincentsty Nov 18 '18 at 9:11
  • 1
    Why would it be suggested to do offchain. Offchain validation can be bypass easily by directly calling to the smart contract function from user wallet address – vincentsty Nov 18 '18 at 9:22
  • 1
    You can take the keccak256 of each array in order to ensure a unique hash. Didn't really understand how this will reduce one for loop. keccak256(array[] addressList1) === keccak256(array[] addressList2) only can check if two array list is same (in terms of length, value and order of value). It cannot identified if an item of an array does exist in another array Or i misunderstand the implementation. – vincentsty Nov 18 '18 at 9:31

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.