Un SSTORE dont la nouvelle valeur est la même que la valeur existante coûte-t-il du gaz?
Par exemple (dans Vyper):
@external
def foo(bar: uint256):
self.baz = bar
foo(3)
foo(3) # Is the gas cost here still 5000?
Vaut-il mieux faire cela à la place?
def foo(bar: uint256):
if self.baz != bar: # Will this check save gas from unnecessary SSTORE's
self.baz = bar
Réponses
Le coût du gaz serait de 200. Il y a en fait un commentaire dans le code source de Geth faisant référence à ce comportement exact. À la protocol_params.go
ligne 46, nous voyons la ligne de code suivante:
NetSstoreNoopGas uint64 = 200 // Once per SSTORE operation if the value doesn't change.
La source: https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go
Le test suivant implique que le coût de stockage de la même valeur est de 800 unités de gaz:
Contrat de solidité:
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();
}
}
Test Truffle 5.x:
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());
}
});
});
L'impression est de 816 pour chaque itération.
En supposant que l' gasleft()
opération de la dernière ligne de la fonction de contrat passe 16 unités de gaz, le coût de stockage de la même valeur semble être de 800 unités de gaz.
Merci aux réponses de @technicallyty et @goodvibration, et la poursuite et l'approche de @ goodvibration sont correctes.
Oui, stocker la même valeur coûte 800 gaz.
EIP-2200 et le code correspondant est en fait la fonction suivante gasSStoreEIP2200
Plus précisément le code:
if current == value { // noop (1)
return params.SloadGasEIP2200, nil
}
qui est commenté comme
// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted.
Et SloadGasEIP2200 est 800:
SloadGasEIP2200 uint64 = 800 // Cost of SLOAD after EIP 2200 (part of Istanbul)
Plus d'information
Voici les commentaires complets sur gasSStoreEIP2200
basés sur 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.
gasSStoreEIP2200
est la fonction actuelle, et gasSStore
est pour historique. (Comme le dit @MrClottom dans sa réponse : "Lorsqu'un nouveau nœud se synchronise et vérifie toutes les transactions, il doit également être capable de comprendre les anciennes règles de consensus ...")
La réponse originale ci-dessous manquait EIP-2200 et était basée sur gasSStore
.
Le code source Geth (branche principale de novembre 2020) et l'état EIP-1283 :
// Le nouveau comptage du gaz est basé sur les coûts nets du gaz (EIP-1283): // //
- Si la valeur actuelle est égale à la nouvelle valeur (ce n'est pas une opération), 200 gaz sont déduits. // 2. Si la valeur actuelle n'est pas égale à la nouvelle valeur // 2.1. Si la valeur d'origine est égale à la valeur actuelle (cet emplacement de stockage n'a pas été modifié par le contexte d'exécution actuel) // 2.1.1. Si la valeur d'origine est 0, 20000 gaz sont déduits. // 2.1.2. Sinon, 5000 gaz sont déduits. Si la nouvelle valeur est 0, ajoutez 15000 gaz au compteur de remboursement. // 2.2. Si la valeur d'origine n'est pas égale à la valeur actuelle (cette fente de stockage est sale), 200 gaz sont déduits. Appliquez les deux clauses suivantes. // 2.2.1. Si la valeur d'origine n'est pas 0 // 2.2.1.1. Si la valeur actuelle est 0 (signifie également que la nouvelle valeur n'est pas 0), retirez 15000 gaz du compteur de restitution. Nous pouvons prouver que le compteur de remboursement ne descendra jamais en dessous de 0. // 2.2.1.2. Si la nouvelle valeur est 0 (signifie également que la valeur actuelle n'est pas 0), ajoutez 15000 gaz au compteur de remboursement. //
2.2.2. Si la valeur d'origine est égale à la nouvelle valeur (cet emplacement de stockage est réinitialisé) // 2.2.2.1. Si la valeur d'origine est 0, ajoutez 19800 gaz au compteur de remboursement. // 2.2.2.2. Sinon, ajoutez 4800 gaz au compteur de remboursement. value: = common.Hash (y.Bytes32 ()) if current == value {// noop (1) return params.NetSstoreNoopGas, nil}