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:
voterList.length--
already has a side effect of setting the "removed" value to zero, so thedelete voterList[voterListLastRow]
is redundant. Adding it costs an extra 5,000 gas (the cost of storing a zero).voterStructs[voterId].voterListPointer = voterList.push(voterId) - 1;
means that the first voter added will have avoterListPointer
value of 0. That means thatdelete 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.)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.