diff --git a/core/state/state_object.go b/core/state/state_object.go index 091d24184a..0b72d01140 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -79,7 +79,7 @@ type stateObject struct { cachedStorage Storage // Storage entry cache to avoid duplicate reads dirtyStorage Storage // Storage entries that need to be flushed to disk - + originalValue Storage // Map of original storage values, at the beginning of current call context // Cache flags. // When an object is marked suicided it will be delete from the trie // during the "update" phase of the state transition. @@ -117,6 +117,7 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject { data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), + originalValue: make(Storage), } } @@ -184,11 +185,16 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash { // SetState updates a value in account storage. func (self *stateObject) SetState(db Database, key, value common.Hash) { + prev := self.GetState(db, key) self.db.journal.append(storageChange{ account: &self.address, key: key, - prevalue: self.GetState(db, key), + prevalue: prev, }) + if _, isSet := self.originalValue[key]; !isSet { + // original value has not been set, so set it now + self.originalValue[key] = prev + } self.setState(key, value) } @@ -210,6 +216,10 @@ func (self *stateObject) updateTrie(db Database) Trie { v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00")) self.setError(tr.TryUpdate(key[:], v)) } + // Clean the map containing 'original' value of storage entries + for k, _ := range self.originalValue { + delete(self.originalValue, k) + } return tr } @@ -280,6 +290,7 @@ func (self *stateObject) deepCopy(db *StateDB) *stateObject { stateObject.code = self.code stateObject.dirtyStorage = self.dirtyStorage.Copy() stateObject.cachedStorage = self.dirtyStorage.Copy() + stateObject.originalValue = self.originalValue.Copy() stateObject.suicided = self.suicided stateObject.dirtyCode = self.dirtyCode stateObject.deleted = self.deleted diff --git a/core/state/statedb.go b/core/state/statedb.go index 101b03a127..d9300012d6 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -169,11 +169,22 @@ func (self *StateDB) Preimages() map[common.Hash][]byte { return self.preimages } +// AddRefund adds gas to the refund counter func (self *StateDB) AddRefund(gas uint64) { self.journal.append(refundChange{prev: self.refund}) self.refund += gas } +// SubRefund removes gas from the refund counter. +// This method will panic if the refund counter goes below zero +func (self *StateDB) SubRefund(gas uint64) { + self.journal.append(refundChange{prev: self.refund}) + if gas > self.refund { + panic("Refund counter below zero") + } + self.refund -= gas +} + // Exist reports whether the given account address exists in the state. // Notably this also returns true for suicided accounts. func (self *StateDB) Exist(addr common.Address) bool { diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f9eea319e1..aee6d6f6d7 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 d176f5b397..a5a3ff3e32 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 b71ead0d77..c7ed2e4515 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) {}