core/vm, params: implement EIP2200, SSTORE optimizations

This commit is contained in:
Péter Szilágyi 2019-08-14 18:29:07 +03:00
parent c2c4c9f1e5
commit 5734445548
No known key found for this signature in database
GPG key ID: E9AE538CEDF8293D
4 changed files with 131 additions and 1 deletions

View file

@ -27,6 +27,8 @@ import (
// defined jump tables are not polluted. // defined jump tables are not polluted.
func EnableEIP(eipNum int, jt *JumpTable) error { func EnableEIP(eipNum int, jt *JumpTable) error {
switch eipNum { switch eipNum {
case 2200:
enable2200(jt)
case 1884: case 1884:
enable1884(jt) enable1884(jt)
case 1344: case 1344:
@ -83,3 +85,8 @@ func opChainID(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo
stack.push(chainId) stack.push(chainId)
return nil, nil return nil, nil
} }
// enable2200 applies EIP-2200 (Rebalance net-metered SSTORE)
func enable2200(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP2200
}

View file

@ -160,6 +160,56 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
return params.NetSstoreDirtyGas, nil return params.NetSstoreDirtyGas, nil
} }
// 1. If current value equals new value (this is a no-op), SSTORE_NOOP_GAS 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_INIT_GAS gas is deducted.
// 2.1.2. Otherwise, SSTORE_CLEAN_GAS gas is deducted. If new value is 0, add SSTORE_CLEAR_REFUND to refund counter.
// 2.2. If original value does not equal current value (this storage slot is dirty), SSTORE_DIRTY_GAS gas is deducted, SSTORE_DIRTY_REFUND is refunded. 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_CLEAR_REFUND 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 SSTORE_CLEAR_REFUND 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_INIT_REFUND to refund counter.
// 2.2.2.2. Otherwise, add SSTORE_CLEAN_REFUND gas to refund counter.
func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
y, x = stack.Back(1), stack.Back(0)
current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
)
value := common.BigToHash(y)
if current == value { // noop (1)
return params.SstoreNoopGasEIP2200, nil
}
original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x))
if original == current {
if original == (common.Hash{}) { // create slot (2.1.1)
return params.SstoreInitGasEIP2200, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200)
}
return params.SstoreCleanGasEIP2200, nil // write existing slot (2.1.2)
}
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
evm.StateDB.SubRefund(params.SstoreClearRefundEIP2200)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200)
}
}
if original == value {
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
evm.StateDB.AddRefund(params.SstoreInitRefundEIP2200)
} else { // reset to original existing slot (2.2.2.2)
evm.StateDB.AddRefund(params.SstoreCleanRefundEIP2200)
}
}
evm.StateDB.AddRefund(params.SstoreDirtyRefundEIP2200)
return params.SstoreDirtyGasEIP2200, nil // dirty update (2.2)
}
func makeGasLog(n uint64) gasFunc { func makeGasLog(n uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
requestedSize, overflow := bigUint64(stack.Back(1)) requestedSize, overflow := bigUint64(stack.Back(1))

View file

@ -16,7 +16,17 @@
package vm package vm
import "testing" import (
"math"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/params"
)
func TestMemoryGasCost(t *testing.T) { func TestMemoryGasCost(t *testing.T) {
tests := []struct { tests := []struct {
@ -37,3 +47,57 @@ func TestMemoryGasCost(t *testing.T) {
} }
} }
} }
var eip2200Tests = []struct {
original byte
input string
used uint64
refund uint64
}{
{0, "0x60006000556000600055", 1612, 0}, // 0 -> 0 -> 0
{0, "0x60006000556001600055", 20812, 0}, // 0 -> 0 -> 1
{0, "0x60016000556000600055", 22312, 20700}, // 0 -> 1 -> 0
{0, "0x60016000556002600055", 22312, 1500}, // 0 -> 1 -> 2
{0, "0x60016000556001600055", 20812, 0}, // 0 -> 1 -> 1
{1, "0x60006000556000600055", 5812, 15000}, // 1 -> 0 -> 0
{1, "0x60006000556001600055", 7312, 5700}, // 1 -> 0 -> 1
{1, "0x60006000556002600055", 7312, 1500}, // 1 -> 0 -> 2
{1, "0x60026000556000600055", 7312, 16500}, // 1 -> 2 -> 0
{1, "0x60026000556003600055", 7312, 1500}, // 1 -> 2 -> 3
{1, "0x60026000556001600055", 7312, 5700}, // 1 -> 2 -> 1
{1, "0x60026000556002600055", 5812, 0}, // 1 -> 2 -> 2
{1, "0x60016000556000600055", 5812, 15000}, // 1 -> 1 -> 0
{1, "0x60016000556002600055", 5812, 0}, // 1 -> 1 -> 2
{1, "0x60016000556001600055", 1612, 0}, // 1 -> 1 -> 1
{0, "0x600160005560006000556001600055", 42318, 20700}, // 0 -> 1 -> 0 -> 1
{1, "0x600060005560016000556000600055", 12318, 20700}, // 1 -> 0 -> 1 -> 0
}
func TestEIP2200(t *testing.T) {
for i, tt := range eip2200Tests {
address := common.BytesToAddress([]byte("contract"))
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.input))
statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original}))
statedb.Finalise(true) // Push the state into the "original" slot
vmctx := Context{
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
}
vmenv := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(big.Int))
if err != nil {
t.Errorf("test %d: failed to run test: %v", i, err)
}
if used := math.MaxUint64 - gas; used != tt.used {
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used)
}
if refund := vmenv.StateDB.GetRefund(); refund != tt.refund {
t.Errorf("test %d: gas refund mismatch: have %v, want %v", i, refund, tt.refund)
}
}
}

View file

@ -52,6 +52,15 @@ const (
NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value
NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value
SstoreNoopGasEIP2200 uint64 = 800 // Once per SSTORE operation if the value doesn't change.
SstoreDirtyGasEIP2200 uint64 = 2300 // Once per SSTORE operation if the slot is already dirty
SstoreDirtyRefundEIP2200 uint64 = 1500 // Once per SSTORE operation if the slot is already dirty
SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero
SstoreInitRefundEIP2200 uint64 = 19200 // Once per SSTORE operation for resetting to the original zero value
SstoreCleanGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else
SstoreCleanRefundEIP2200 uint64 = 4200 // Once per SSTORE operation for resetting to the original non-zero value
SstoreClearRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot
JumpdestGas uint64 = 1 // Once per JUMPDEST operation. JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.