3

For example (in Vyper):

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

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

Is it better to do this instead?

def foo(bar: uint256):
  if self.baz != bar: # Will this check save gas from unnecessary SSTORE's
      self.baz = bar
  • Watch out from counting on the 200 gas assumption provided in the answer that you've accepted. I believe that it may apply to older EVM versions (prior Istanbul or something like that), where SLOAD used to cost 200 gas. In the current version, SLOAD costs 800 gas, so I have a reason to suspect that the 200 gas in your specific case may also have changed to 800 gas (as measured in my test example). – goodvibration Nov 20 '20 at 10:01
3

The gas cost would be 200. There is actually a comment in the source code of Geth referring to this exact behavior. In protocol_params.go on line 46 we see the following line of code:

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

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

  • do you know if this is true across all clients? This answers for Geth, though theoretically other clients could be different, no? – The Renaissance Nov 22 '20 at 12:15
  • 1
    @TheRenaissance The correct answer is by goodvibration. All clients have to implement the same gas cost, they have to be in consensus, otherwise there would be multiple blockchains... One client can't charge you differently from another client: everyone has to agree how much you charged, how much you paid, how much funds you have left :) – eth Dec 13 '20 at 9:14
  • @eth Thanks for the answer - I agree that opcodes have one standard price across clients, but thought that this might be a gray zone, and different clients might handle it differently. – The Renaissance Dec 14 '20 at 13:58
2

The following test implies that the cost of storing the same value is 800 gas units:

Solidity Contract:

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

Truffle 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());
        }
    });
});

The printout is 816 for every iteration.

Assuming that the gasleft() operation in the last line of the contract function spends 16 gas units, the cost of storing the same value appears to be 800 gas units.

  • Interesting. The EVM for this contract may need to be checked, or post as a question. The current Geth source indicates 200 gas. – eth Nov 20 '20 at 10:18
  • 1
    @eth: Yeah, I'm about to post a question on that. – goodvibration Nov 20 '20 at 10:24
  • I think calling the gasLeft() function costs extra gas. – Dylan Kerler Nov 20 '20 at 10:48
  • @DylanKerler: Maybe, but I doubt that it costs 616 gas. – goodvibration Nov 20 '20 at 10:52
  • 1
    You're right I'm updating my answer... Sorry for delay. Feel free to comment on it if I might be mistaken. (I can't take my upvote on the other answer back.) – eth Dec 13 '20 at 8:17
1

Thank you to the answers by @technicallyty and @goodvibration, and @goodvibration's continuation and approach is correct.

Yes, storing the same value costs 800 gas.

EIP-2200 and the relevant code is actually the next function gasSStoreEIP2200

More precisely the code:

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

which is commented as

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

And SloadGasEIP2200 is 800:

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

More information

Here are the full comments on gasSStoreEIP2200 based on 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 is the current function, and gasSStore is for historical. (As @MrClottom says in his answer: "When a new node syncs and verifies all transactions it also has to be able to understand old consensus rules...")

Original answer below was missing EIP-2200 and was based on gasSStore.

The Geth source code (master branch Nov 2020) and EIP-1283 state:

// The new gas metering is based on net gas costs (EIP-1283): // //

  1. If current value equals new value (this is a no-op), 200 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, 20000 gas is deducted. // 2.1.2. Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter. // 2.2. If original value does not equal current value (this storage slot is dirty), 200 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), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0. // 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 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 19800 gas to refund counter. // 2.2.2.2. Otherwise, add 4800 gas to refund counter. value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.NetSstoreNoopGas, nil }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.