solidity – Applying Delete to Complete Storage Ref[] in Solidity with One Call

arrayscontract-designcontract-developmentsoliditystruct

Definition:

 mapping(address => data)         clusterContract;

 struct data { //defined inside a Library.
    mapping(address => Library.Struct[]) my_status;
 }
 data list;     
 clusterContract[id] = list;

Usage:

clusterContract[msg.sender].my_status[id].push( Library.Struct({ status: status_ }));
delete clusterContract[msg.sender].my_status; //<=error occurs.

Error occurs:

Error: Unary operator delete cannot be applied to type 
mapping(address => struct ReceiptLib.Status storage ref[] storage ref)
    E               delete clusterContract[msg.sender].my_status;

On the other hand: delete clusterContract[msg.sender].my_status[id] works.

=> Does delete clusterContract[msg.sender] removes(initialise to 0) complete array of my_status as well?

[Q] How could I apply delete to complete storage ref[]? or do I required to iterate ref[](keep track of the ids) and apply delete one by one as shown on the example?

Example:

for(int i=0;i<storedIDs.size();i++)
   delete clusterContract[msg.sender].my_status[storedIDs[i]];

Thank you for your valuable time and help.

Best Answer

Deleting data does free up state space on clients using State Tree Pruning and should be done where possible when ever it is no longer required.

However mappings cannot simply be deleted in a single operation as the contract itself doesn't know where the data resides in the state address. For that it requires the key to be provided in order to delete the individually mapped elements.

Unbounded arrays, such as uint[] bytes etc, can be deleted in the one solidity operation however, the EVM must iteratively delete each slot individually. Care must be taken not to let such arrays grow too big because a future bulk delete operation may exhaust the block gas limit and brick your contract. The lesson here being that your garbage collection should also be designed to be atomic.

Storing new data is at a cost of 20000 per slot while overwriting data costs only 5000. Deleting data also costs 5000 per slot but returns a 15000 gas per slot refund at the end of your call.

What Rob has suggested is good practice with arrays because you can reuse reinitialised storage simply by setting length to 0. However you aren't using arrays but mappings which is a whole other ball game.

So, from your code fragments...

struct data {
    //defined inside a Library.
    mapping(address => Library.Struct[]) my_status;
}

mapping(address => data) clusterContract;
data list;     
clusterContract[id] = list;

...

clusterContract[msg.sender].my_status[id].push( Library.Struct({ status: status_ }));

when you try:

delete clusterContract[msg.sender].my_status; //<=error occurs.

you are trying to delete an entire mapping in one go because .mystatus is defined as:

mapping(address => Library.Struct[]) my_status;

but a mapping itself can't be deleted, only it's elements.

When I work with mappings of structs in particular, I write deconstructors to clean them up. So something like:

function destroy(id) internal

{
   delete clusterContract[msg.sender].my_status[storedIDs[id]]; 
}

But again try to design toward atomic operations with known life cycles rather than having to rely on expensive and messy bulk cleanup.