9

Consider the following contract that illustrates the problem. Here we have a dynamic array of mappings, add() is meant to add a fresh new mapping to the end of the array, return the value for mapping key 0 and change that value to true. remove() in turn is meant to remove the last mapping from an array.

contract ClearMapping {
    mapping(uint => bool)[] state;

    // 1. add -> false
    // 2. remove
    // 3. add -> true
    function add() returns (bool) {
        uint pos = state.length++;
        bool curr = state[pos][0];
        state[pos][0] = true;
        return curr;
    }

    function remove() {
        state.length--;
    }
}

One can think that removing a mapping element from an array effectively clears that mapping, and that adding a new mapping in place of the old one will have a fresh, all key* -> false, element. It turns out that it is not true. Same thing happens if you try to swap elements that have mappings inside, everything will be swapped except of mappings.

The only solution I see to deal with this, is by manually delete/reassign every used mapping key, but it becomes costly in terms of gas usage very fast.

The questions I'm looking to answer is:

  1. Is this a bug in EVM/Solidity?
  2. Is there a convenient/effective way to clear/swap mappings?
  3. Should I not use mappings as temporary KeyValue instances?

Thanks for the attention!

1 Answer 1

5

In order to answer your question let me explain how the EVM stack actually looks like: Itself is just a map from from a key to a value, both 32 byte long.

a solidity map, maps from sha3(mapId . key) to a given value on the evm stack. That is also the reason one cannot iterate through all keys in a map, because they are "randomized" through the whole evm stack.

a solidity array maps from sha3(arrayId) + index to a value. Here we can iterate if we know the arrayId by just incrementing the index.

(Here I'm not really sure if the length of an array is saved as well.)

The following is just speculation as I'm note entirely sure how solidity solves this: if you now have an array of maps, what you actually do is mapId = sha3(arrayId) + index. You can iterate through them. However if you save something to a map you do the following:

sha3((sha3(arrayId) + index) . key ) = value

But by removing the last element of the map you loose the array id for the map, but because one cannot iterate through maps without knowing the keys your value is preserved.

However, multiarrays don't have this problem:

import "dapple/test.sol";

contract A is Test {
  uint[][] multiarray;

  function testMultiArray() {
    //@log multiarray length: `uint multiarray.length`
    //@log incrementing multiarray
    multiarray.length++;
    //@log multiarray length: `uint multiarray.length`
    //@log multiarray[0] length: `uint multiarray[0].length`
    //@log incrementing multiarray[0]
    multiarray[0].length++;
    multiarray[0].length++;
    //@log multiarray[0][0]: `uint multiarray[0][0]`
    //@log set value to 1
    multiarray[0][0] = 1;
    multiarray[0][1] = 1;
    //@log multiarray[0] length: `uint multiarray[0].length`
    //@log multiarray[0][0]: `uint multiarray[0][0]`
    //@log multiarray[0][1]: `uint multiarray[0][1]`
    //@log decrementing multiarray
    multiarray.length--;
    //@log multiarray[0] length: `uint multiarray.length`
    //@log incrementing multiarray
    multiarray.length++;
    //@log multiarray[0] length: `uint multiarray[0].length`
    multiarray[0].length++;
    multiarray[0].length++;
    //@log multiarray[0][0]: `uint multiarray[0][0]`
    //@log multiarray[0][1]: `uint multiarray[0][1]`
  }
}

Will produce the following output:

  test multi array
  LOG:  multiarray length: 0
  LOG:  incrementing multiarray
  LOG:  multiarray length: 1
  LOG:  multiarray[0] length: 0
  LOG:  incrementing multiarray[0]
  LOG:  multiarray[0][0]: 0
  LOG:  set value to 1
  LOG:  multiarray[0] length: 2
  LOG:  multiarray[0][0]: 1
  LOG:  multiarray[0][1]: 1
  LOG:  decrementing multiarray
  LOG:  multiarray[0] length: 0
  LOG:  incrementing multiarray
  LOG:  multiarray[0] length: 0
  LOG:  multiarray[0][0]: 0
  LOG:  multiarray[0][1]: 0
3
  • Thanks for the explanation! Now I can tell that with this storage architecture it is not a bug(question 1). And I knew that it is alright for the arrays, though you can't effectively substitute all kind of mappings with arrays. What about question 2? As for 3, I decided to not use mappings in temporary entities for now. Apr 1, 2016 at 15:41
  • 1
    2. simple => no. If you want to clear a mapping, you have to remember and iterate through all keys and delete it manually with delete map[key]. There is an iteratable pattern which consists out of a mpping and an array, which lets you iterate through your map, which can be used for deleting the elemens. Also you can come up with your own data structures to remove mapped elements, but evm has no garbage collection build in, you will have to do it manually.
    – Denis
    Apr 1, 2016 at 15:43
  • That what I was doing after finding this out. But the gas usage is increasing dramatically, which is no go for production. Maybe there is a way to substitute them? For example to substitute key => bool one can use uint[] as a collection of batches of 256 bools each. But what about key => uint or others? Apr 1, 2016 at 15:51

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.