1

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.

alper
  • 8,395
  • 11
  • 63
  • 152

2 Answers2

3

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.

o0ragman0o
  • 4,320
  • 18
  • 35
2

Delete doesn't do very much to the underlying data because this is an append-only storage system. No matter what we do, the data is still there in a previous world state.

Since you want to remove all elements in the array in one move:

pragma solidity ^0.4.6;

contract NullifyArray {

    address[] public status;

    function pushArray(address data)
        public
        returns(uint arrayLength)
    {
        return status.push(data);
    }

    function getArrayLength() 
        public 
        constant
        returns (uint arrayLength)
    {
        return status.length;
    }

    function nullifyArray()
        public
        returns(uint arrayLength)
    {
        status.length=0;
        return status.length;
    }

}

This will leave the basic structure in place, i.e. the struct still has an array, but it's length is 0 and no elements can be accessed by the public getter. It did nothing to erase the data that exists on the chain but it's logically gone.

If you need to be selective about which array rows to remove, I might suggest the Mapped Structs with Delete-enabled Index pattern here: Are there well-solved and simple storage patterns for Solidity?

Hope it helps.

Rob Hitchens
  • 55,151
  • 11
  • 89
  • 145
  • Smart and this solution will use minimum gas. So data never be deleted, but logically we can put conditions for clients not to access the data. So for example when I getArrayValue() if status.length is 0 (even it was 100 before delete is done), I can do throw for the user who want to access the data? The thing is there could be delete operation and via same id it can be re-created. @Rob Hitchens – alper Apr 07 '17 at 07:39
  • Please note that I am looking for solution to complete remove Array. But definitely I will look into the link you shared. @Rob Hitchens – alper Apr 07 '17 at 07:57
  • 1
    On the read-back side, if array.length==0 and a function tries to access array[0], the EVM will throw for you. – Rob Hitchens Apr 07 '17 at 08:01
  • So basically status.length = 0 do the job. – alper Apr 07 '17 at 08:19
  • Yip. And it does so at a predictable and consistent gas cost at any scale. – Rob Hitchens Apr 07 '17 at 08:30
  • But there is a problem. it my case I need to apply your solution on all my_status array my_status[id].length. Since I do not know the keys, I do also need to store them to be able to access them again to initialise their length to 0. @Rob Hitchens – alper Apr 07 '17 at 08:41
  • No. Not a problem, or not as stated. IMO, problem isn't what to do when the keys aren't known. If the data isn't organized in a way that supports the use-case then life will indeed get very difficult. You want to obliterate all status arrays for all Id? That can done in one move given an appropriate data structure. Let me know if I've got that right, because it sounds like an interesting challenge. – Rob Hitchens Apr 07 '17 at 09:16
  • Yes sir you got it right. I want to obliterate all status arrays for all Id that have been created before. @Rob Hitchens – alper Apr 07 '17 at 09:32
  • I don't see an obvious way to maintain the relationships and also obliterate it all in one move. What you can do is ensure you can iterate the contract addresses. Keep a list. Instead of trying to iterate inside the contract, use a trusted client to iterate the Ids, destroying all the linked status arrays as it goes. That will keep the gas cost per transaction consistent ... you'll just have lots of transactions in the "job". Ideally, large scale reorganization is something to avoid if possible. – Rob Hitchens Apr 07 '17 at 10:46
  • It does not have to be on one move. Thank you I got the main idea @Rob Hitchens – alper Apr 07 '17 at 11:01
  • 1
    @Rob Hitchens Deleting data does free up space on clients using State Tree Pruning and should be done when ever possible to reclaim disk space. – o0ragman0o Apr 07 '17 at 11:40
  • @o0ragman0o Thanks for that! Where can I find an ELI5 description of current status as implemented? I found this, referring to VB's article from 2015. I'm a little uncertain about the what is included in the current protocol. http://ethereum.stackexchange.com/questions/174/what-is-state-tree-pruning-and-how-does-it-work – Rob Hitchens Apr 07 '17 at 11:47
  • @Rob Hitchens I don't know myself and haven't seen much published on it so I think its might be something where clients may differ in their implementation of it. – o0ragman0o Apr 07 '17 at 12:11