Kostet ein SSTORE, bei dem der neue Wert dem vorhandenen Wert entspricht, Gas?

Nov 19 2020

Zum Beispiel (in Vyper):

@external
def foo(bar: uint256):
  self.baz = bar

foo(3)
foo(3) # Is the gas cost here still 5000?

Ist es besser, dies stattdessen zu tun?

def foo(bar: uint256):
  if self.baz != bar: # Will this check save gas from unnecessary SSTORE's
      self.baz = bar

Antworten

3 technicallyty Nov 19 2020 at 23:48

Die Gaskosten würden 200 betragen. Es gibt tatsächlich einen Kommentar im Quellcode von Geth, der sich auf dieses genaue Verhalten bezieht. In protocol_params.goZeile 46 sehen wir die folgende Codezeile:

NetSstoreNoopGas uint64 = 200 // Once per SSTORE operation if the value doesn't change.

Quelle: https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go

2 goodvibration Nov 20 2020 at 07:22

Der folgende Test impliziert, dass die Kosten für die Speicherung des gleichen Werts 800 Gaseinheiten betragen:

Soliditätsvertrag:

pragma solidity 0.6.12;

contract MyContract {
    uint256 public gasUsed;
    uint256 public storageSlot;
    function func(uint256 x) public {
        storageSlot = x;
        uint256 gasLeft = gasleft();
        storageSlot = x;
        gasUsed = gasLeft - gasleft();
    }
}

Trüffel 5.x Test:

const MyContract = artifacts.require("MyContract");

contract("MyContract", accounts => {
    it("test", async () => {
        const myContract = await MyContract.new();
        for (let x = 0; x < 10; x++) {
            await myContract.func(x);
            const gasUsed = await myContract.gasUsed();
            console.log(gasUsed.toString());
        }
    });
});

Der Ausdruck ist 816 für jede Iteration.

Unter der Annahme, dass der gasleft()Vorgang in der letzten Zeile der Vertragsfunktion 16 Gaseinheiten ausgibt, scheinen die Kosten für die Speicherung des gleichen Werts 800 Gaseinheiten zu betragen.

1 eth Nov 20 2020 at 10:04

Vielen Dank für die Antworten von @technicallyty und @goodvibration, und die Fortsetzung und Vorgehensweise von @ goodvibration ist korrekt.

Ja, die Speicherung des gleichen Wertes kostet 800 Gas.

EIP-2200 und der entsprechende Code ist eigentlich die nächste Funktion gasSStoreEIP2200

Genauer gesagt der Code:

if current == value { // noop (1)
    return params.SloadGasEIP2200, nil
}

das ist kommentiert als

// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted.

Und SloadGasEIP2200 ist 800:

SloadGasEIP2200 uint64 = 800  // Cost of SLOAD after EIP 2200 (part of Istanbul)

Mehr Informationen

Hier sind die vollständigen Kommentare gasSStoreEIP2200zu EIP-2200:

// 0. If *gasleft* is less than or equal to 2300, fail the current call.
// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted.
// 2. If current value does not equal new value:
//   2.1. If original value equals current value (this storage slot has not been changed by the current execution context):
//     2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted.
//     2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter.
//   2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses:
//     2.2.1. If original value is not 0:
//       2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter.
//       2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter.
//     2.2.2. If original value equals new value (this storage slot is reset):
//       2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
//       2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.

gasSStoreEIP2200ist die aktuelle Funktion und gasSStoreist für historische. (Wie @MrClottom in seiner Antwort sagt : "Wenn ein neuer Knoten alle Transaktionen synchronisiert und überprüft, muss er auch in der Lage sein, alte Konsensregeln zu verstehen ...")

Die ursprüngliche Antwort unten fehlte EIP-2200 und basierte auf gasSStore.

Der Geth-Quellcode (Hauptzweig Nov 2020) und der EIP-1283- Status:

// Die neue Gasmessung basiert auf den Nettogaskosten (EIP-1283): // //

  1. Wenn der aktuelle Wert dem neuen Wert entspricht (dies ist ein No-Op), werden 200 Gase abgezogen. // 2. Wenn der aktuelle Wert nicht dem neuen Wert entspricht // 2.1. Wenn der ursprüngliche Wert dem aktuellen Wert entspricht (dieser Speichersteckplatz wurde vom aktuellen Ausführungskontext nicht geändert) // 2.1.1. Wenn der ursprüngliche Wert 0 ist, werden 20000 Gas abgezogen. // 2.1.2. Andernfalls werden 5000 Gase abgezogen. Wenn der neue Wert 0 ist, fügen Sie 15000 Gas zum Rückerstattungszähler hinzu. // 2.2. Wenn der ursprüngliche Wert nicht dem aktuellen Wert entspricht (dieser Speicherplatz ist verschmutzt), werden 200 Gase abgezogen. Wenden Sie beide folgenden Klauseln an. // 2.2.1. Wenn der ursprüngliche Wert nicht 0 ist // 2.2.1.1. Wenn der aktuelle Wert 0 ist (bedeutet auch, dass der neue Wert nicht 0 ist), entfernen Sie 15000 Gas aus dem Rückerstattungszähler. Wir können nachweisen, dass der Rückerstattungszähler niemals unter 0 fällt. // 2.2.1.2. Wenn der neue Wert 0 ist (bedeutet auch, dass der aktuelle Wert nicht 0 ist), fügen Sie dem Rückerstattungszähler 15000 Gas hinzu. //
    2.2.2. Wenn der ursprüngliche Wert dem neuen Wert entspricht (dieser Speichersteckplatz wird zurückgesetzt) ​​// 2.2.2.1. Wenn der ursprüngliche Wert 0 ist, fügen Sie 19800 Gas zum Rückerstattungszähler hinzu. // 2.2.2.2. Andernfalls fügen Sie 4800 Gas zum Rückerstattungszähler hinzu. value: = common.Hash (y.Bytes32 ()) wenn current == value {// noop (1) params.NetSstoreNoopGas, nil} zurückgibt