8

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.

2

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.

  • I think that this answer should be marked as the correct one and that the first one is wrong. – Nic Szerman Jun 15 '18 at 12:24
  • @smarx. Thanks for turning my attention back to this after all this time. – Rob Hitchens - B9lab Dec 18 '18 at 3:22
7

Appendix G. Fee Schedule in the Yellow Paper-EIP-150, shows that a gas refund of 15,000 is given for each state slot that is set to zero. These refunds are calculated during execution but only paid back post-execution.

The Fee Schedule also states that changing a store from a Zero value costs 20,000 but changing a non-Zero store costs only 5000. The notable difference being the 15,000 refund.

So the net cost of a store and delete is 5000 gas. If the entire 20,000 gas or more was refunded, then there is effectively no net cost for storage operations and its IO overhead. A cheap attack could be launched by continuous store/delete.

So to answer the questions...

A1 A delete is a store operation costing 20,000 but with 15,000 refunded.

A2 Your delete operations are setting 3 slots to Zero, for an after refund cost of 15000. Two are Voter struct slots and the third is the address array slot.

A3 The storage related gas costs are more a disincentive to write to storage the first time. I agree that this also acts as a disincentive for garbage collection, but I don't know how else it could be done without becoming an attack vector.

I still feel strongly that the developer should garbage collect as a way to maintain the 'commons' of storage. But as you note, the way its done, could lead to a tragedy of the commons, i.e. unrecoverable state bloat increasing with time.

  • I appreciate the well-considered, well-explained and well-sourced explanation. Thanks! – Rob Hitchens - B9lab Apr 27 '17 at 15:26
  • Sorry to join this party so late, but I'm pretty sure this answer is wrong. Deleting things from storage definitely saves gas. See my answer. – user19510 Mar 16 '18 at 3:43
  • @smarx I as well think that this answer is wrong. It's hard to believe that it got so many upvotes, I now wonder how many other wrong answers were also successful. – Nic Szerman Jun 15 '18 at 12:22
  • Agreed I didn't see the bugs in this particular code that @smarx has described, but what do you see as wrong with my description of how gas refunds work? – o0ragman0o Jun 17 '18 at 1:42
  • Well, for example this is wrong: "A delete is a store operation costing 20,000 but with 15,000 refunded." Deletion (storing a zero where there used to be a non-zero) costs 5,000 and adds 15,000 to the refund counter. – user19510 Dec 18 '18 at 3:38

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.