1

I have a function that I need to pass an arbitrary length array of addresses to. In remix, just calling a function that has an address[] costs around 30k gas for an array of 5 addresses:

function fun(address[] calldata addresses) external returns (address[] memory) {
    return addresses;
}

The addresses that I want to call are of a finite set of about 2000 addresses, but it could be any of those addresses when I call my function, so I thought that I could store them in a mapping of uint=>address and call my function with an array of uint16's that I can then look up the addresses with (that I previously stored). Theoretically this should be much cheaper because the calldata is way smaller:

pragma solidity 0.5.17;

contract StoreIDToAddress {
    
    mapping(uint => address) public numToAddress;

    
    function getAddresses(uint16[] calldata  _IDs) external view returns (address[] memory) {
        address[] memory arr = new address[](_IDs.length);
        
        for (uint i; i < _IDs.length; i++) {
            arr[i] = numToAddress[_IDs[i]];
        }
        
        return arr;
    }

    // Other functions to initialize numToAddress etc
}

getAddresses costs 29378 gas with an input of [0, 1, 2, 3, 4]. This alone makes it no cheaper than just including the addresses themselves in the calldata, and when I tested with a 2nd contract that takes in an uint16[] and passes it to getAddresses it costs 34196 gas:

    function testUint16Arr3(uint16[] calldata _arr) external returns (address[] memory) {
        return storeIDToAddress.getAddresses(_arr);
    }

Using bytes and decoding that in to a uint16 to call getAddresses is 35095 gas.

So my question is, why does getAddresses cost so much gas when (AFAIK) the biggest operation is 5 x SLOAD = 1000 gas? How can I reduce the gas cost of getting addresses in to my functions?

  • The transaction itself costs a minimum of 21000, and your gas-measurement method probably doesn't take that into account. Add uint256 public gasUsed in your contract, then add uint256 gasLeft = gasleft(); at the beginning of your function, and then add gasUsed = gasLeft - gasleft(); at the end of your function. Then, after calling your function from the offchain, call gasUsed() in order to check how much gas the function really used. – goodvibration Jun 29 at 18:17
  • Note that they might have renamed gasleft() in solc 0.4.x to gasLeft() in solc 0.5.x, so you might need to use different names in my example above, depending on your solc version. – goodvibration Jun 29 at 18:19
  • Yes you're right, and I had been using gasleft() for 0.5.x but I was still surprised at the ~9k+ of gas attributed to dealing with storage variables – the_cranky_panda Jun 29 at 20:31
2

The Istanbul hardfork included EIP-1884, which (among other repricings) repriced SLOAD from 200 to 800 gas. That means loading 5 addresses from storage will cost 4000 gas, not including any overhead. The Istanbul hardfork also included EIP-2028, which lowered the cost of non-zero calldata bytes from 68 to 16. These two EIPS are what cause the huge difference in gas costs from what you are expecting.

Also as @goodvibration mentioned in a comment, there is an inbuilt cost of 21000 gas with all transactions. This is the base cost, and any contract interaction you do is on-top of this amount.

| improve this answer | |
  • I see, thank you. I suppose this is to help prevent further bloat of the state? – the_cranky_panda Jun 29 at 20:32

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.