Löschen des Index eines Arrays im Hinblick auf die Optimierung für Schleifen

Ich habe eine Dapp, wo ein Administrator eine Reihe von Kuchen hochladen kann (20 bis 30). Die Leute können auf die Website kommen und entscheiden, ob sie einen vom Administrator hochgeladenen Kuchen kaufen möchten.

Also habe ich für Smart Contract ein Array erstellt uint, um jedes Mal zu speichern, idwenn der CakeAdministrator hochlädt. Sobald der Käufer einen der Kuchen kaufte, würde er den idaus Cakedem Array löschen. Ich habe mich entschieden, es zu löschen, um Schleifen so weit wie möglich zu vermeiden, da dies dazu führen würde, dass kein Benzin mehr vorhanden ist, während ich das Array iteriere, um die verbleibende Kuchenliste an die Käufer zurückzugeben. Wie sieht es in Sachen Optimierung aus?

Unten ist meine Implementierung. Gibt es bessere Lösungen oder reicht das um weiterzumachen?

struct Cake {
    uint id;
    address buyer;
}

mapping (uint => Cake) public cakes;
uint[] public cakeIds;

function addNewCake(uint _id) public {
    cakes[_id] = Cake(_id, 0x0);

    cakeIds.push(_id);
}

function buyCake(uint _id) public {      
    Cake storage cake = cakes[_id];       
    cake.buyer = msg.sender;

    removeCakeInArray(_id);
}

function removeCakeInArray(uint _id) private {
    for (uint i = 0; i <= getNumOfCakes(); i++) {            
        if (cakeIds[i] == _id) {
            remove(i);                 
        }    
    }
}

function remove(uint index) private {
    if (index >= getNumOfCakes()) return;

    for (uint i = index; i < getNumOfCakes() - 1; i++){
        cakeIds[i] = cakeIds[i+1];
    }
    cakeIds.length--;
}

function getAllUnsoldCakes() public view returns (uint[]) {
    uint length = getNumOfCakes();  
    uint[] memory ids = new uint[](length);

    for (uint i = 0; i < length; i++) {       
        uint cakeId = cakeIds[i];
        Cake memory cake = cakes[cakeId];

        if (cake.buyer == 0x0) {
            ids[i] = cake.id;             
        }
    }

    return ids;
}

function getNumOfCakes() public view returns (uint) {
    return cakeIds.length;
}

Antworten (2)

Diese Schleifen, insbesondere in remove(), sind ein Warnsignal: Ihr Array kann auf eine unbegrenzte Länge anwachsen, auch wenn jemand es böswillig erhöht, bei der Ihr Vertrag an die Gasgrenzen stößt und unbrauchbar wird. Dies ist nur in Ordnung, wenn Sie die Länge der Liste explizit einschränken können - zum Beispiel, wenn jemand versucht, einen 11. unverkauften Kuchen der Transaktion hinzuzufügen revert, weil die Regale der Konditorei voll sind.

Wenn Sie wirklich in der Lage sein müssen, eine Liste nicht verkaufter Kuchen mit einem Solidity-Funktionsaufruf abzurufen, ist es sicherer, eine verknüpfte Liste zu verwenden, da diese unabhängig von der Länge der Liste für die gleichen Kosten aktualisiert werden kann. Dies gibt Ihnen immer noch Gaskosten, über die Sie sich Gedanken machen müssen, wenn Sie die Liste lesen, aber das kann in Ordnung sein, wenn es nur zum Lesen von Daten von einem Knoten verwendet wird, anstatt als Teil einer Transaktion aufgerufen zu werden.

Aber müssen Sie wirklich in der Lage sein, mit einem Vertragsfunktionsaufruf eine Liste unverkaufter Kuchen zu erhalten? Es kann besser sein, diese Arbeit an einen anderen Ort zu übertragen - zum Beispiel Ereignisse zu protokollieren, wenn Sie einen Kuchen hinzufügen oder verkaufen, diese Ereignisse dann aus Ihrem Dapp-Javascript lesen und daraus die Liste erstellen, die Sie dem Benutzer anzeigen.

Danke für deine Kommentare. Ich bin mir nicht sicher, warum remove()könnte eine rote Fahne sein. Was sind die Szenarien, in denen jemand ein Array absichtlich vergrößern kann?
Ich denke, wenn addNewCake()auf den Administrator beschränkt ist (was in Ihrem Code nicht der Fall ist) und ihnen völlig vertraut wird und der gesamte Vertrag sowieso gebrochen wird, ist ihr Schlüssel gestohlen, dann sind Sie in Ordnung. Wenn eines dieser Dinge nicht zutrifft, kann ein böswilliger Benutzer einfach anrufen, addNewCake()bis das Array lang genug ist, dass der Loop-In remove()mehr Gas verbraucht als das Block-Gas-Limit.
Macht Sinn. Außerdem denke ich, um die nicht verkauften Kuchen anzurufen, zuerst .json-Dateien zu verwenden, um alle Kuchen anzuzeigen und sie als verkauft zu markieren, wenn der Kuchen von einem Käufer verkauft wird (speichern Sie einfach die Adresse und die Kuchen-ID des Käufers in der Datei Blockchain). Würde es dadurch glatter werden?
Verkauft oder nicht wirkt sich darauf aus, ob die Transaktion durchgeführt wird, also würde ich diesen Teil wahrscheinlich in der Blockchain machen, aber das Einfügen von menschenlesbaren Details (Name des Kuchens usw.) Sinn.

Unterstütze Edmonds Bedenken bezüglich der Schleifen. Meiner Meinung nach ist die Notwendigkeit, die Liste aufzuzählen, fraglich. Der Vertrag könnte auf einer Nur-Anhang-Basis mit einem einfachen Flag funktionieren, um "verkauft" und Ereignisse anzuzeigen, die chronische Hinzufügungen und Entfernungen sind. Die Kunden könnten dann selbst berechnen, welche Kuchen noch zum Verkauf stehen, und der Vertrag würde lediglich eine Regel durchsetzen, dass kein Kuchen zweimal verkauft werden darf. Ebenso können Bestelllisten in der Regel außerhalb von Lagerverträgen liegen.

Für den Fall, dass ein anderer Vertrag daran interessiert sein könnte, erfolgreich durch die Liste zu navigieren, müssen Sie diese Informationen möglicherweise ohne Abhängigkeit von Ereignissen effizient auffindbar machen. Dies würde die Sorge "Löschen" einschließen.

Betrachten Sie in diesem Fall das hier beschriebene Mapped Struct with Delete-Muster: Gibt es gut gelöste und einfache Speichermuster für Solidity? mit einer ausführlichen Beschreibung hier: https://medium.com/@robhitchens/solidity-crud-part-1-824ffa69509a

Ich hoffe es hilft.