Gas Cost – Why Does Delete Increase Overall Gas Cost?

contract-designgasgas-refundstorage

My understanding is there is a gas refund for delete and the idea is to encourage developers to remove garbage from contract states.

Consider this contract with a delete-enabled index of keys to mapped structs. The delete operations in deleteVoter() are not necessary for the function to work as required.

When the deletes are included, gas is about 34056 for a deleteVoter(). With the deletes commented out, the contract is as functional as before, but deleteVoter() only costs about 23926 gas. So, the deleteVoter() function is more gas-efficient without the delete garbage collection.

Q1: Why are the deletes adding to the cost?

Q2: Am I missing something about optimizing the two delete operations that seem to be adding to the gas cost?

Q3: Am I possibly missing the point of the gas refund? I expected the refund from the deletes to bubble up into a net savings so the cost of deleteVoter() would decrease. That is, incentivize garbage collection.

pragma solidity 0.4.11;

contract IterableMappingWithDelete {

  struct Voter {
    uint votesCast;                                         // application data
    uint voterListPointer;                                  // structurally important
  }

  mapping(address => Voter) public voterStructs;            // random access by key
  address[] public voterList;                               // sequential access by row and count

  function getVoterCount() public constant returns(uint voterCount) {return voterList.length;}

  function isVoter(address voterId) public constant returns(bool isIndeed) {
    if(voterList.length==0) return false;
    return voterList[voterStructs[voterId].voterListPointer]==voterId; 
  }

  function insertVoter(address voterId) public returns(bool success) {
    if(isVoter(voterId)) throw;
    voterStructs[voterId].voterListPointer = voterList.push(voterId) - 1;
    return true;
  }

  function deleteVoter(address voterId) public returns(bool success) {
    if(!isVoter(voterId)) throw;
    uint rowToDelete = voterStructs[voterId].voterListPointer;
    uint voterListLastRow = voterList.length-1;
    address keyToMove = voterList[voterListLastRow];
    voterStructs[keyToMove].voterListPointer = rowToDelete;
    voterList[rowToDelete] = keyToMove;

    // The next line is optional garbage collection. 
    // It increases the cost of this operation. 

    delete voterStructs[voterId];

    voterList.length--;
    return true;
  }

}

This question is updated from the original post so the snippet works as intended. The gas figures are also updated.

Best Answer

Deleting something in storage (setting a non-zero to a zero) does indeed lower the gas cost, but there are a couple things in your code that are making this hard to see:

  1. voterList.length-- already has a side effect of setting the "removed" value to zero, so the delete voterList[voterListLastRow] is redundant. Adding it costs an extra 5,000 gas (the cost of storing a zero).
  2. voterStructs[voterId].voterListPointer = voterList.push(voterId) - 1; means that the first voter added will have a voterListPointer value of 0. That means that delete voterStructs[voterId] on that first voter will again just be an extra 5,000 gas. (No refund will be given because the value was already zero.)
  3. The votesCast field in this code is always 0, so clearing it is always an extra 5,000 gas.

By adding those two lines, then, you're adding 15,000 gas (5,000 * 3 because you're writing three zeros) and getting an extra gas refund of 10,000 or 15,000 (depending on whether it's the first voter in the array). Recall that the gas refund can give you up to half of your consumed gas back, so even in the best case, you won't actually break even.

Related Topic