Ist es praktisch, Mappings als temporäre KeyValue-Instanzen zu verwenden?

Betrachten Sie den folgenden Vertrag, der das Problem veranschaulicht. Hier haben wir ein dynamisches Array von Mappings, add()das am Ende des Arrays ein frisches neues Mapping hinzufügen, den Wert für Mapping key zurückgeben 0und diesen Wert in ändern soll true. remove()wiederum soll die letzte Zuordnung aus einem Array entfernen.

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--;
    }
}

Man kann sich vorstellen, dass das Entfernen eines Mapping-Elements aus einem Array dieses Mapping effektiv löscht und dass das Hinzufügen eines neuen Mappings anstelle des alten ein neues, all key* -> false, -Element hat. Es stellt sich heraus, dass es nicht stimmt. Dasselbe passiert, wenn Sie versuchen, Elemente auszutauschen, die Mappings enthalten, alles wird außer Mappings ausgetauscht.

Die einzige Lösung, die ich sehe, um damit umzugehen, besteht darin, jeden verwendeten Zuordnungsschlüssel manuell zu löschen / neu zuzuweisen, aber es wird sehr schnell kostspielig in Bezug auf den Gasverbrauch.

Die Fragen, die ich beantworten möchte, sind:

  1. Ist das ein Fehler in EVM/Solidity?
  2. Gibt es eine bequeme/effektive Möglichkeit, Zuordnungen zu löschen/auszutauschen?
  3. Sollte ich Mappings nicht als temporäre KeyValue-Instanzen verwenden?

Danke für ihre Aufmerksamkeit!

Antworten (1)

Um Ihre Frage zu beantworten, lassen Sie mich erklären, wie der EVM-Stack tatsächlich aussieht: An sich ist nur eine Abbildung von einem Schlüssel zu einem Wert, beide 32 Byte lang.

eine solidity map, bildet von sha3(mapId . key) auf einen gegebenen Wert auf dem evm Stack ab. Das ist auch der Grund, warum man nicht durch alle Schlüssel in einer Map iterieren kann, weil sie durch den gesamten evm-Stack "randomisiert" werden.

Ein Solidity-Array bildet sha3(arrayId) + index auf einen Wert ab. Hier können wir iterieren, wenn wir die arrayId kennen, indem wir einfach den Index erhöhen.

(Hier bin ich mir nicht sicher, ob auch die Länge eines Arrays gespeichert wird.)

Das Folgende ist nur Spekulation, da ich mir absolut sicher bin, wie solidity dies löst: Wenn Sie jetzt ein Array von Karten haben, ist das, was Sie tatsächlich tun, mapId = sha3(arrayId) + index. Sie können sie durchlaufen. Wenn Sie jedoch etwas auf einer Karte speichern, gehen Sie wie folgt vor:

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

Aber durch das Entfernen des letzten Elements der Karte verlieren Sie die Array-ID für die Karte, aber da man Karten nicht durchlaufen kann, ohne die Schlüssel zu kennen, bleibt Ihr Wert erhalten.

Multiarrays haben dieses Problem jedoch nicht:

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]`
  }
}

Wird die folgende Ausgabe erzeugen:

  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
Danke für die Erklärung! Jetzt kann ich sagen, dass es sich bei dieser Speicherarchitektur nicht um einen Fehler handelt (Frage 1). Und ich wusste, dass es für die Arrays in Ordnung ist, obwohl Sie nicht alle Arten von Mappings effektiv durch Arrays ersetzen können. Was ist mit Frage 2? Wie bei 3 habe ich mich entschieden, vorerst keine Zuordnungen in temporären Entitäten zu verwenden.
2. einfach => nein. Wenn Sie eine Zuordnung löschen möchten, müssen Sie sich alle Schlüssel merken und durchlaufen und sie manuell mit delete map[key] löschen. Es gibt ein iterierbares Muster, das aus einem Mpping und einem Array besteht, mit dem Sie durch Ihre Map iterieren können, das zum Löschen der Elemente verwendet werden kann. Sie können auch Ihre eigenen Datenstrukturen erstellen, um gemappte Elemente zu entfernen, aber evm hat keine eingebaute Garbage Collection, Sie müssen dies manuell tun.
Das tat ich, nachdem ich das herausgefunden hatte. Aber der Gasverbrauch steigt dramatisch an, was für die Produktion nicht geeignet ist. Vielleicht gibt es eine Möglichkeit, sie zu ersetzen? Zum Beispiel key => boolkann man uint[]als Ersatz eine Sammlung von Stapeln von jeweils 256 bools verwenden. Aber was ist mit key => uintoder anderen?