15

Fixed question:

I have a mapping:

mapping (bytes32 => mapping (bytes32 => bytes32[])) items;

With some elements in it:

items["fruits"]["apples"].push("first apple");
items["fruits"]["apples"].push("second apple");
items["fruits"]["apples"].push("third apple");
items["fruits"]["plums"].push("first plum");
items["vegetables"]["carrots"].push("first carrot");

I want to delete all fruits. The code below does not work:

delete items["fruits"]

The error I get is:

Error: Unary operator delete cannot be applied to type mapping(bytes32 => bytes32)

Original question:

I have a mapping:

mapping (bytes32 => mapping (bytes32 => bytes32)) items;

With some elements in it:

items["fruits"]["apples"] = "first apple";
items["fruits"]["apples"] = "second apple";
items["fruits"]["apples"] = "third apple";
items["fruits"]["plums"] = "first plum";
items["vegetables"]["carrot"] = "first carrot";

I want to delete all fruits. The code below does not work:

delete items["fruits"]

The error I get is:

Error: Unary operator delete cannot be applied to type mapping(bytes32 => bytes32)

5 Answers 5

19

Unfortunately you can't delete a mapping. From the solidity docs at: http://solidity.readthedocs.io/en/develop/types.html

delete has no effect on whole mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted.

So you could do something like delete items[i][j] but you would need to know i and j in advance.

One common pattern is to keep an additional array (e.g. bytes32[]) into which you store the keys to your mapping, which you could then iterate over. Or in your case you could keep a mapping from "fruits" to an array of fruits, and then iterate over this when you want to delete all fruits. You would need to maintain these secondary data structures as you add fruits to your main items mapping.

Hope this helps!

3
  • That's exactly what I was doing, thank you for answering and confirming it's the right way indeed. Apr 26, 2017 at 21:38
  • how much gas could be expected for such an operation? any kind of estimation? Aug 29, 2017 at 21:54
  • There is a workaround for deleting a mapping. Mar 19, 2018 at 3:54
19

All problems in computer science can be solved by another level of indirection.

enter image description here

pragma solidity ^0.4.20;

contract DeletableMapping {
    // Input is keccak256(uint32 mappingVersion, string carVIN)
    mapping (bytes32 => string) carfaxReports;
    uint32 currentMappingVersion;

    function getCarfaxReport(string _carVIN) external view returns(string) {
        bytes32 key = keccak256(currentMappingVersion, _carVIN);
        return carfaxReports[key];
    }

    function setCarfaxReport(string _carVIN, string _reportJSON) external {
        bytes32 key = keccak256(currentMappingVersion, _carVIN);
        carfaxReports[key] = _reportJSON;
    }

    function deleteAllReports() external {
        currentMappingVersion++;
    }

    function recoverGas(uint32 _version, string _carVIN) external {
        require(_version < currentMappingVersion);
        bytes32 key = keccak256(_version, _carVIN);
        delete(carfaxReports[key]);
    }
}
3
  • 2
    Extra points for illustration! Jun 28, 2018 at 1:41
  • The recoverGas function can be restricted to onlyOwner depending on your use cases Jan 21, 2019 at 2:59
  • Great solution - Love it! (right now this solution is striking me as kinda brilliant actually...) Kudos!
    – Mark55
    Mar 5, 2019 at 21:48
4

Agree with everything @AdamAID said. Just to elaborate ...

Update:

There is a hidden assumption/confusion in the question. The example isn't valid.

This bit:

items["fruits"]["apples"] = "first apple";
items["fruits"]["apples"] = "second apple";
items["fruits"]["apples"] = "third apple";

That's just stomping on the same location/key three times; not storing as separate values as I imagine you want. Since you want fruits=>apples=>list of apples that exist, we need to structure the data a little differently.

Unless I miss my guess, you're going for three(?) one-to-many relationships, items has many categories (items are fruits, cars, houses ... ), categories has many species (categories are apples, plums, pairs...), and then there is a list of instances ... first apple, second apply, third apple ...

In a minimal implementation, you'll end up with an array in the mix:

items["fruits"]["apples"].instanceList[0] = "first apple";
items["fruits"]["apples"].instanceList[1] = "second apple";
items["fruits"]["apples"].instanceList[2] = "third apple";

I'm not sure I can do this justice in a small space, but you can find some explanation here: https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42

General idea; park a list of instances in each items[][] location:

In this case, it is possible to destroy all apples with delete items["fruits"]["apples"]; or orphan them with items["fruits"]["apples"].instanceList.length=0;

It doesn't destroy the mapping. It doesn't really destroy the arrays stored inside the mapping. That's the structural stuff that doesn't go away. It does make the arrays stored in the mapped location empty, so no more apples.

Organizing the data so you can easily do what needs to be done is crucial.

Hope it helps.

Original Answer

You're defining a state variable (the mapping). Not something you want to think about as a transient variable. It can be "empty" (so to speak) but it can't be destroyed. At most, you can delete a key/value pair with mappedThing[key].

Ethereum is an append-only system, so even a delete operation is in fact a new transaction appended to the end of the chain (in a block). This changes the way we think about delete. For example, it's not possible to actually remove the previous state. A determined adversary would be able to discover the previous value at a previous block even after the delete has been applied.

There are some advantages to explicitly deleting mapped values because this informs others the data is no longer important (as opposed to writing a 0). Doing so is incentivized with negative gas cost.

Commonly, there is an index to mapped keys (because mappings themselves aren't iterable). Something like bytes32[] public keyList.

There is a common approach to logical delete; simply set a bool (e.g. isActive=false) inside the mapped struct. Under that approach, you don't really want to delete the element. You'll just skip "deleted" records as you find them, e.g. Mapped Structs with Index.

A less common approach is to actually remove the key from the list. That will prevent uncontrolled growth of the list in the case that logical deletes are a common occurrence in your app, e.g. Mapped Structs with Delete-enabled Index

We can even imagine sorted indexes (e.g. linked list) and a logical splice operation that removes a key and thereby makes the mapped data inaccessible via the contract functions.

In summary, logical removal or records/objects may be sufficient:

  1. If you're relying on a flag to indicate "deleted" then you obviously can't actually delete the element from the mapping (things would break).
  2. If you're using a delete-capable index scheme then removal of a key from the index is sufficient. It's not necessary, but it's possible to also delete a key/value pair from the mapping. Not possible to completely destroy the mapping.

Both approaches to logical delete are elaborated here. Are there well-solved and simple storage patterns for Solidity?

Hope it helps.

11
  • Thank you, that's very useful! Yes, I understand that what's published, stays there forever, by "deleting" I meant wiping references, so the data won't be available for retrieval from the smart contract. Apr 26, 2017 at 21:42
  • So, essentially, items["fruits"] is an element in items mapping, which itself references a mapping data structure. I want to "disable" retrieving that nested mapping data structure using the items["fruits"] key. Apr 26, 2017 at 21:49
  • 1
    I understand what you're saying about the nested mappings. The namespace for items exists and cannot be destroyed even though possibly no values have been set (sort of empty). Return values always default to zero-ish. At any given first-level key there is another mapping. It's also a namespace that can't be destroyed. The state variables are a lot like defining a schema/organization structure. Those mappings can't be destroyed. Specific key/value pairs stored there can be deleted. Hope I'm not adding confusion :-) Apr 26, 2017 at 22:11
  • 1
    It would be prudent to try and delete data that is no longer used. This has an effect on the size of on disk storage for lite and slim clients.
    – o0ragman0o
    Apr 26, 2017 at 23:48
  • 1
    The referential integrity idea uses key lists in addition to mapped structs, so you would have of such things. The destruction of a category would be accomplished by making the list length 0 or deleting the mapped struct. You can use the pattern recursively to represent all the one-to-many joins. The "many" related items can be deleted en masse if you make a function to do that. Something like delete all child things related to parent where the parent instances contain a list of related children. Apr 26, 2017 at 23:50
0
pragma solidity 0.8.20;

contract Iterable {

    uint public dataset;
    uint public currentIndex;

    mapping (uint => mapping (uint => bool)) public NestedIndex;

    function updateMappingSet() public {
        ++dataset;
        currentIndex = 0;
    }

    function addValue(bool boolean) public {
        unchecked {
            NestedIndex[dataset][currentIndex++] = boolean;
        }
    }
}

Admittedly this doesn't delete the mapping (as mappings can't really be deleted), however it's a convenient way of 'resetting' the mapping index without too much fuss (for use with programs that a require a map key within a linear index range).

-1

I highly recommend against William's solution as it keeps data on chain that shouldn't be kept. Considering the data is stored on thousands of computers across the globe, keeping storage down is essential. Please delete unnecessary data like Adam's answer, you'll also receive a gas refund for doing so

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.