From 58374e28d95c03d8b0e6d9035c0fb92fad3e865e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 11 Aug 2018 23:03:54 +0200 Subject: core, state: initial implementation of Eip-1283 --- core/vm/gas_table.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++- core/vm/interface.go | 1 + core/vm/noop.go | 1 + 3 files changed, 60 insertions(+), 1 deletion(-) (limited to 'core/vm') diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f9eea319e..aee6d6f6d 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -17,9 +17,11 @@ package vm import ( + "bytes" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" + "math/big" ) // memoryGasCosts calculates the quadratic gas for memory expansion. It does so @@ -115,7 +117,7 @@ func gasReturnDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack * return gas, nil } -func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSStoreOld(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) val = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) @@ -137,6 +139,61 @@ func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m } } +func gasSStore(gt params.GasTable, 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)) + ) + //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. + new := common.BigToHash(y) + if current == new { + // 1. current == new + return 200, nil + } + // Todo, get this value + original := common.Hash{} + + // 2 + if original == current { // 2.1 + if original == (common.Hash{}){ // 2.1.1 + return 20000, nil + } + // 2.1.2 + if new == (common.Hash{}){ + evm.StateDB.AddRefund(15000) + } + return 5000, nil + } + // 2.2 + if original != (common.Hash{}){ // 2.2.1 + if current == (common.Hash{}){ // 2.2.1.1 + evm.StateDB.SubRefund(15000) + }else{ + // 2.2.1.2 + evm.StateDB.AddRefund(15000) + } + } + if original == new { // 2.2.2 + if original == (common.Hash{}){ + evm.StateDB.AddRefund(19800) + }else{ + evm.StateDB.AddRefund(4800) + } + } + return 200, nil +} + func makeGasLog(n uint64) gasFunc { return func(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { requestedSize, overflow := bigUint64(stack.Back(1)) diff --git a/core/vm/interface.go b/core/vm/interface.go index d176f5b39..a5a3ff3e3 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -40,6 +40,7 @@ type StateDB interface { GetCodeSize(common.Address) int AddRefund(uint64) + SubRefund(uint64) GetRefund() uint64 GetState(common.Address, common.Hash) common.Hash diff --git a/core/vm/noop.go b/core/vm/noop.go index b71ead0d7..c7ed2e451 100644 --- a/core/vm/noop.go +++ b/core/vm/noop.go @@ -56,6 +56,7 @@ func (NoopStateDB) GetCode(common.Address) []byte func (NoopStateDB) SetCode(common.Address, []byte) {} func (NoopStateDB) GetCodeSize(common.Address) int { return 0 } func (NoopStateDB) AddRefund(uint64) {} +func (NoopStateDB) SubRefund(uint64) {} func (NoopStateDB) GetRefund() uint64 { return 0 } func (NoopStateDB) GetState(common.Address, common.Hash) common.Hash { return common.Hash{} } func (NoopStateDB) SetState(common.Address, common.Hash, common.Hash) {} -- cgit v1.2.3 From caa2c23a38141911a570ba098a940b4fdbf0aa88 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 12 Aug 2018 14:47:03 +0200 Subject: core,state: finish implementing Eip 1283 --- core/vm/gas_table.go | 29 +++++++++++++---------------- core/vm/interface.go | 1 + core/vm/jump_table.go | 8 ++++++++ core/vm/noop.go | 1 + 4 files changed, 23 insertions(+), 16 deletions(-) (limited to 'core/vm') diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index aee6d6f6d..77250978d 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -17,11 +17,9 @@ package vm import ( - "bytes" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" - "math/big" ) // memoryGasCosts calculates the quadratic gas for memory expansion. It does so @@ -117,7 +115,7 @@ func gasReturnDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack * return gas, nil } -func gasSStoreOld(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) val = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) @@ -139,10 +137,11 @@ func gasSStoreOld(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack } } -func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +// gasSStoreEip1283 calculates SSTORE gas cost according to EIP-1283 +func gasSStoreEip1283(gt params.GasTable, 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)) + y, x = stack.Back(1), stack.Back(0) + current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) ) //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 @@ -161,33 +160,31 @@ func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m // 1. current == new return 200, nil } - // Todo, get this value - original := common.Hash{} - + original := evm.StateDB.GetStateOriginal(contract.Address(), common.BigToHash(x)) // 2 if original == current { // 2.1 - if original == (common.Hash{}){ // 2.1.1 + if original == (common.Hash{}) { // 2.1.1 return 20000, nil } // 2.1.2 - if new == (common.Hash{}){ + if new == (common.Hash{}) { evm.StateDB.AddRefund(15000) } return 5000, nil } // 2.2 - if original != (common.Hash{}){ // 2.2.1 - if current == (common.Hash{}){ // 2.2.1.1 + if original != (common.Hash{}) { // 2.2.1 + if current == (common.Hash{}) { // 2.2.1.1 evm.StateDB.SubRefund(15000) - }else{ + } else { // 2.2.1.2 evm.StateDB.AddRefund(15000) } } if original == new { // 2.2.2 - if original == (common.Hash{}){ + if original == (common.Hash{}) { evm.StateDB.AddRefund(19800) - }else{ + } else { evm.StateDB.AddRefund(4800) } } diff --git a/core/vm/interface.go b/core/vm/interface.go index a5a3ff3e3..2e2e3e925 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -44,6 +44,7 @@ type StateDB interface { GetRefund() uint64 GetState(common.Address, common.Hash) common.Hash + GetStateOriginal(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) Suicide(common.Address) bool diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index deedf70cd..8a997adc4 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -95,6 +95,14 @@ func newConstantinopleInstructionSet() [256]operation { writes: true, returns: true, } + instructionSet[SSTORE] = operation{ + execute: opSstore, + gasCost: gasSStoreEip1283, + validateStack: makeStackFunc(2, 0), + valid: true, + writes: true, + } + return instructionSet } diff --git a/core/vm/noop.go b/core/vm/noop.go index c7ed2e451..19539d978 100644 --- a/core/vm/noop.go +++ b/core/vm/noop.go @@ -59,6 +59,7 @@ func (NoopStateDB) AddRefund(uint64) func (NoopStateDB) SubRefund(uint64) {} func (NoopStateDB) GetRefund() uint64 { return 0 } func (NoopStateDB) GetState(common.Address, common.Hash) common.Hash { return common.Hash{} } +func (NoopStateDB) GetStateOriginal(common.Address, common.Hash) common.Hash { return common.Hash{} } func (NoopStateDB) SetState(common.Address, common.Hash, common.Hash) {} func (NoopStateDB) Suicide(common.Address) bool { return false } func (NoopStateDB) HasSuicided(common.Address) bool { return false } -- cgit v1.2.3 From 5d921fa3a0cea9d87e7fd391c0ddd3115d00d0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 18 Sep 2018 16:24:35 +0300 Subject: core, params: polish net gas metering PR a bit --- core/vm/gas_table.go | 117 +++++++++++++++++++++++-------------------------- core/vm/interface.go | 2 +- core/vm/jump_table.go | 8 ---- core/vm/logger_test.go | 5 --- core/vm/noop.go | 72 ------------------------------ 5 files changed, 55 insertions(+), 149 deletions(-) delete mode 100644 core/vm/noop.go (limited to 'core/vm') diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 77250978d..10b4f719a 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -116,79 +116,70 @@ func gasReturnDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack * } func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - var ( - y, x = stack.Back(1), stack.Back(0) - val = evm.StateDB.GetState(contract.Address(), common.BigToHash(x)) - ) - // This checks for 3 scenario's and calculates gas accordingly - // 1. From a zero-value address to a non-zero value (NEW VALUE) - // 2. From a non-zero value address to a zero-value address (DELETE) - // 3. From a non-zero to a non-zero (CHANGE) - if val == (common.Hash{}) && y.Sign() != 0 { - // 0 => non 0 - return params.SstoreSetGas, nil - } else if val != (common.Hash{}) && y.Sign() == 0 { - // non 0 => 0 - evm.StateDB.AddRefund(params.SstoreRefundGas) - return params.SstoreClearGas, nil - } else { - // non 0 => non 0 (or 0 => 0) - return params.SstoreResetGas, nil - } -} - -// gasSStoreEip1283 calculates SSTORE gas cost according to EIP-1283 -func gasSStoreEip1283(gt params.GasTable, 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)) ) - //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. - new := common.BigToHash(y) - if current == new { - // 1. current == new - return 200, nil - } - original := evm.StateDB.GetStateOriginal(contract.Address(), common.BigToHash(x)) - // 2 - if original == current { // 2.1 - if original == (common.Hash{}) { // 2.1.1 - return 20000, nil + // The legacy gas metering only takes into consideration the current state + if !evm.chainRules.IsConstantinople { + // This checks for 3 scenario's and calculates gas accordingly: + // + // 1. From a zero-value address to a non-zero value (NEW VALUE) + // 2. From a non-zero value address to a zero-value address (DELETE) + // 3. From a non-zero to a non-zero (CHANGE) + switch { + case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 + return params.SstoreSetGas, nil + case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 + evm.StateDB.AddRefund(params.SstoreRefundGas) + return params.SstoreClearGas, nil + default: // non 0 => non 0 (or 0 => 0) + return params.SstoreResetGas, nil + } + } + // 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.BigToHash(y) + if current == value { // noop (1) + return params.NetSstoreNoopGas, nil + } + original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x)) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.NetSstoreInitGas, nil } - // 2.1.2 - if new == (common.Hash{}) { - evm.StateDB.AddRefund(15000) + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) } - return 5000, nil - } - // 2.2 - if original != (common.Hash{}) { // 2.2.1 - if current == (common.Hash{}) { // 2.2.1.1 - evm.StateDB.SubRefund(15000) - } else { - // 2.2.1.2 - evm.StateDB.AddRefund(15000) + return params.NetSstoreCleanGas, 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.NetSstoreClearRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) } } - if original == new { // 2.2.2 - if original == (common.Hash{}) { - evm.StateDB.AddRefund(19800) - } else { - evm.StateDB.AddRefund(4800) + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + evm.StateDB.AddRefund(params.NetSstoreResetClearRefund) + } else { // reset to original existing slot (2.2.2.2) + evm.StateDB.AddRefund(params.NetSstoreResetRefund) } } - return 200, nil + return params.NetSstoreDirtyGas, nil } func makeGasLog(n uint64) gasFunc { diff --git a/core/vm/interface.go b/core/vm/interface.go index 2e2e3e925..fc15082f1 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -43,8 +43,8 @@ type StateDB interface { SubRefund(uint64) GetRefund() uint64 + GetCommittedState(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash - GetStateOriginal(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) Suicide(common.Address) bool diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 8a997adc4..deedf70cd 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -95,14 +95,6 @@ func newConstantinopleInstructionSet() [256]operation { writes: true, returns: true, } - instructionSet[SSTORE] = operation{ - execute: opSstore, - gasCost: gasSStoreEip1283, - validateStack: makeStackFunc(2, 0), - valid: true, - writes: true, - } - return instructionSet } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 28830c445..cdbb70dc4 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -41,11 +41,6 @@ func (d *dummyContractRef) SetBalance(*big.Int) {} func (d *dummyContractRef) SetNonce(uint64) {} func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } -type dummyStateDB struct { - NoopStateDB - ref *dummyContractRef -} - func TestStoreCapture(t *testing.T) { var ( env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) diff --git a/core/vm/noop.go b/core/vm/noop.go deleted file mode 100644 index 19539d978..000000000 --- a/core/vm/noop.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -func NoopCanTransfer(db StateDB, from common.Address, balance *big.Int) bool { - return true -} -func NoopTransfer(db StateDB, from, to common.Address, amount *big.Int) {} - -type NoopEVMCallContext struct{} - -func (NoopEVMCallContext) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return nil, nil -} -func (NoopEVMCallContext) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return nil, nil -} -func (NoopEVMCallContext) Create(caller ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { - return nil, common.Address{}, nil -} -func (NoopEVMCallContext) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { - return nil, nil -} - -type NoopStateDB struct{} - -func (NoopStateDB) CreateAccount(common.Address) {} -func (NoopStateDB) SubBalance(common.Address, *big.Int) {} -func (NoopStateDB) AddBalance(common.Address, *big.Int) {} -func (NoopStateDB) GetBalance(common.Address) *big.Int { return nil } -func (NoopStateDB) GetNonce(common.Address) uint64 { return 0 } -func (NoopStateDB) SetNonce(common.Address, uint64) {} -func (NoopStateDB) GetCodeHash(common.Address) common.Hash { return common.Hash{} } -func (NoopStateDB) GetCode(common.Address) []byte { return nil } -func (NoopStateDB) SetCode(common.Address, []byte) {} -func (NoopStateDB) GetCodeSize(common.Address) int { return 0 } -func (NoopStateDB) AddRefund(uint64) {} -func (NoopStateDB) SubRefund(uint64) {} -func (NoopStateDB) GetRefund() uint64 { return 0 } -func (NoopStateDB) GetState(common.Address, common.Hash) common.Hash { return common.Hash{} } -func (NoopStateDB) GetStateOriginal(common.Address, common.Hash) common.Hash { return common.Hash{} } -func (NoopStateDB) SetState(common.Address, common.Hash, common.Hash) {} -func (NoopStateDB) Suicide(common.Address) bool { return false } -func (NoopStateDB) HasSuicided(common.Address) bool { return false } -func (NoopStateDB) Exist(common.Address) bool { return false } -func (NoopStateDB) Empty(common.Address) bool { return false } -func (NoopStateDB) RevertToSnapshot(int) {} -func (NoopStateDB) Snapshot() int { return 0 } -func (NoopStateDB) AddLog(*types.Log) {} -func (NoopStateDB) AddPreimage(common.Hash, []byte) {} -func (NoopStateDB) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) {} -- cgit v1.2.3