0

I'm currently working on a marketplace demo for the consensys bootcamp, you can find my entire code here.

Stores inside the marketplace (which are structs) are stored inside a mapping and the keys pointing to the stores are stored in an array. I have a function that removes a store and I wanted to test gas usage.

I have 2 different approaches:

  • the first one just deletes the item id array and sets a bool flag to false
  • the second approach just uses the delete keyword to delete the struct

.

/// @notice removes an existing store
/// @dev only callable by store owners, requires store to be active
/// @param _storeID ID of store to be removed
/// @return true on success, false on failure
function removeStore(bytes32 _storeID) public onlyStoreOwner returns(bool) {
    // storage pointers to store and store id list
    Store storage store = stores[msg.sender][_storeID];
    bytes32[] storage storeIdList = storeIdLists[msg.sender];

    // store has to be active in order to be removed
    require(store.active, "store currently not active");

    // copy last index of store id list to index of to be removed store and delete last item of array
    storeIdList[store.index] = storeIdList[storeIdList.length-1];
    storeIdList.length--;
    // remove items from store
    store.itemIdList.length = 0;
    store.active = false;

    emit RemovedStore(msg.sender, _storeID);
    return true;
}

function removeStore2(bytes32 _storeID) public onlyStoreOwner returns(bool) {
    // storage pointers to store and store id list
    Store storage store = stores[msg.sender][_storeID];
    bytes32[] storage storeIdList = storeIdLists[msg.sender];

    // store has to be active in order to be removed
    require(store.active, "store currently not active");

    // copy last index of store id list to index of to be removed store and delete last item of array
    storeIdList[store.index] = storeIdList[storeIdList.length-1];
    storeIdList.length--;
    // remove items from store
    delete stores[msg.sender][_storeID];

    emit RemovedStore(msg.sender, _storeID);
    return true;
}

when I test the gas usage of both functions I generate a new store with the name "test" and the description "test". The item id list is empty and the bool flag gets set to true.

the removeStore function has a transaction cost of 26641 gas

removeStore info

as expected, the bool flag is set to false, everything else stays.

removeStore variables

now the removeStore2 function has a transaction cost of 37230 gas

removeStore2 info

again, everything gets deleted as expected

removeStore2 variables

Now to my question:

why does the second function consume more gas? the delete keyword sets 2 more non-zero storages to zero. shouldn't there be a gas refund? The transactions were tested with ganache-cli and the remix javascript vm.

  • Have a look over here? ethereum.stackexchange.com/questions/15573/… – Rob Hitchens - B9lab Dec 17 '18 at 23:08
  • I don't see how that question relates. The issue in that code was doing the same deletion work twice. – smarx Dec 18 '18 at 0:02
  • yeah now that I looked into the other issue I don't see why it costs more gas.. I think it might have to do something with deleting arrays. check this code out in remix: gist.github.com/gretzke/f1a1a8b6d662e2da4d137e79271422cf the function deleting the larger array costs way more gas than the one removing the smaller array. I don't see the reason why, as I am setting more values, definitely from non-zero to zero – Daniel Gretzke Dec 18 '18 at 0:07
  • Working on an answer now. Give me 5-10 minutes. – smarx Dec 18 '18 at 0:09
  • Answered below. @RobHitchensB9lab it would be nice if you would unmark the wrong answer in that other question. I think people get misled by it frequently. – smarx Dec 18 '18 at 0:27
1

tl;dr Once you've passed the point where the gas refunds exceed half the total gas used, doing more deletion actually costs more.

If you build a minimal repro of the issue, you get to something like this:

pragma solidity 0.5.1;

contract Test {
    uint256 a = 1;
    uint256 b = 1;

    // gas cost ~= 13000
    function deleteOne() external {
        a = 0;
    }

    // gas cost ~= 15500    
    function deleteBoth() external {
        a = 0;
        b = 0;
    }

    function reset() external {
        a = 1;
        b = 1;
    }
}

(When testing the two functions, be sure to reset() in between.)

This seems surprising at first because we mostly think of deleting (zeroing out storage) as having a negative gas cost, but it actually doesn't. Changing a value in storage from a non-zero value to a zero value has two effects on gas:

  1. It costs 5,000 gas.
  2. It adds 15,000 to the refund counter.

At the end of the transaction, the refund counter is used to subtract up to half of the gas fees.

So in the case of deleteOne:

  • Base transaction fee is 21,000.
  • Writing a zero to storage costs 5,000 gas.
  • Changing a non-zero to a zero adds 15,000 to the refund counter.
  • Total gas fees: 21,000+5,000 = 26,000.
  • After refund: 26000 - min(26000/2, 15000) = 13000

In the case of deleteBoth:

  • Base transaction fee is 21,000.
  • Writing two zeros to storage costs 5,000*2=10,000 gas.
  • Changing two non-zeros to zeros adds 15,000*2=30,000 to the refund counter.
  • Total gas fees: 21,000+10,000 = 31,000.
  • After refund: 31000 - min(31000/2, 30000) = 15500

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.