새로운 가치가 기존 가치와 동일한 SSTORE는 가스 비용이 듭니까?

Nov 19 2020

예 (Vyper에서) :

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

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

대신 이것을하는 것이 더 낫습니까?

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

답변

3 technicallyty Nov 19 2020 at 23:48

가스 비용은 200입니다. 실제로 Geth의 소스 코드에는이 정확한 동작을 언급하는 주석이 있습니다. 에서 protocol_params.go라인 (46)에 우리는 다음 코드 줄을 참조하십시오

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

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

2 goodvibration Nov 20 2020 at 07:22

다음 테스트는 동일한 값을 저장하는 비용이 가스 단위 800 개라는 것을 의미합니다.

견고성 계약 :

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 테스트 :

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

출력은 모든 반복에 대해 816입니다.

gasleft()계약 함수의 마지막 라인에서 작업이 16 개의 가스 단위를 소비 한다고 가정하면 동일한 값을 저장하는 비용은 800 개의 가스 단위로 나타납니다.

1 eth Nov 20 2020 at 10:04

@technicallyty 및 @goodvibration의 답변에 감사 드리며 @goodvibration의 지속 및 접근 방식은 정확합니다.

예, 동일한 값을 저장하는 데는 800 가스가 듭니다.

EIP-2200 및 관련 코드는 실제로 다음 기능인 gasSStoreEIP2200입니다.

더 정확하게는 코드 :

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

다음 과 같이 주석 이 달린

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

그리고 SloadGasEIP2200는 800 :

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

추가 정보

gasSStoreEIP2200EIP-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현재 기능이며 gasSStore역사적 기능입니다. (@MrClottom이 그의 대답 에서 말했듯이 : "새 노드가 모든 트랜잭션을 동기화하고 확인할 때 이전 합의 규칙도 이해할 수 있어야합니다 ...")

아래의 원래 답변은 EIP-2200이 누락되었으며 gasSStore.

Geth 소스 코드 (마스터 지점 2020년 11월) 및 EIP-1283 상태 :

// 새 가스 계량은 순 가스 비용 (EIP-1283)을 기반으로합니다. // //

  1. 현재 값이 새 값과 같으면 (이는 작동하지 않음) 200 가스가 차감됩니다. // 2. 현재 값이 새 값과 같지 않은 경우 // 2.1. 원래 값이 현재 값과 같은 경우 (이 스토리지 슬롯은 현재 실행 컨텍스트에 의해 변경되지 않음) // 2.1.1. 원래 값이 0이면 20000 가스가 차감됩니다. // 2.1.2. 그렇지 않으면 5000 가스가 공제됩니다. 새 값이 0이면 환불 카운터에 15000 가스를 추가하십시오. // 2.2. 원래 값이 현재 값과 같지 않으면 (이 저장 슬롯이 더러움) 200 가스가 차감됩니다. 다음 절을 모두 적용하십시오. // 2.2.1. 원래 값이 0이 아닌 경우 // 2.2.1.1. 현재 값이 0이면 (새 값이 0이 아님을 의미 함) 환불 카운터에서 15000 가스를 제거합니다. 환불 카운터가 0 미만이되지 않음을 증명할 수 있습니다. // 2.2.1.2. 새 값이 0이면 (현재 값이 0이 아님을 의미 함) 환불 카운터에 15000 가스를 추가합니다. //
    2.2.2. 원래 값이 새 값과 같으면 (이 스토리지 슬롯이 재설정 됨) // 2.2.2.1. 원래 값이 0이면 19800 가스를 환불 카운터에 추가하십시오. // 2.2.2.2. 그렇지 않으면 4800 가스를 환불 카운터에 추가하십시오. value : = common.Hash (y.Bytes32 ()) if current == value {// noop (1) return params.NetSstoreNoopGas, nil}