4

My contract currently serves an array of addresses to a front-end:

address[] public addresses;
function getAddresses() public view returns (address[]) {
    return addresses;
}

I'm concerned about scalability. Is there any limit to the length of an array of addresses that can be returned? Would it be better to store all addresses in a mapping, with a counter?

mapping public (uint => address) addressMap;
uint public addressCounter;

This seems like it would be more scalable, but would require the front-end to make many more calls to the contract, which then uses each address to make yet another call.

4

There are trade-offs between the different types of indexed storage and many of us find that we need to use a combination of the options to accomplish all a given contract's goals.

There is a scalability problem with passing the whole list in one move. There's no need to do that when the client possibly already has most of the list. In any case, you'll find scale is achieved when all operations are arranged as fixed-cost functions.

Even though your example is simple, I'll show you how to accomplish the most common long-term storage needs (long-term, because one doesn't normally get to amend the contract later).

Iterable list, like your example, but return them individually:

function getAddressCount() public view returns(uint count) {
  return addesses.length;
}

function getAddressAtRow(uint row) public view returns(address theAddress) {
  return addresses[row];
}

You might (very likely) find it handy to know (without iteration) if an address is somewhere in addresses[] to avoid issues like appending the same address twice. You want the contract to catch an errant client request, so it needs a way to do that at scale (no iteration). Mappings can help. In addition to the array:

mapping(address => bool) bool isActuallyAnAddressOnMyList;

That accomodates something like:

function isAddress(address check) public view returns(bool isIndeed) {
   return isActuallyAnAddressOnMyList[check];
}

You can get a little fancier with a pattern that will accomodate additional properties about the addresses (as many properties as you need):

struct AddressStruct {
  bool isAddress;
  // more fields
}

mapping(address => AddressStruct) public addressStructs;
address[] public addressList;

You can make the contract state completely discoverable:

function getAddressCount() public constant returns(uint count) {
  return addressList.length;
}
function getAddressStruct(address fetch) public view returns(bool isAddress, ... ) {
  require(isAddress(fetch);
  return(addressStructs[fetch].isAddress, ...);
}

You can (should) emit events as changes happen:

event LogNewAddress(address sender, address newAddress);

function appendAddress(address newAddress) returns(bool success) {
  require(!isAddress(newAddress));
  addressList.push(newAddress);
  addressStructs[newAddress].isAddress = true;
  LogNewAddress(msg.sender, newAddress);
  return true;
}

By emitting the event, you give clients that are already synced a way to simply take note of new addresses as they arrive. It also means it is theoretically possible to reconstruct the entire state history of the contract when done properly.

The idea of using both mapping and array increases the insertion gas cost but it addresses the tradeoffs of using one or the other exclusively. Various structures and tradeoffs are described over here: Are there well-solved and simple storage patterns for Solidity?

Hope it helps.

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.