mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
core, params: implement EIP-1087, net storage gas metering
This commit is contained in:
parent
cf540d38a6
commit
3acffa944d
16 changed files with 213 additions and 95 deletions
|
|
@ -154,7 +154,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
|
||||||
return nil, errBlockNumberUnsupported
|
return nil, errBlockNumberUnsupported
|
||||||
}
|
}
|
||||||
statedb, _ := b.blockchain.State()
|
statedb, _ := b.blockchain.State()
|
||||||
val := statedb.GetState(contract, key)
|
val, _ := statedb.GetState(contract, key)
|
||||||
return val[:], nil
|
return val[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,10 @@ type (
|
||||||
prev uint64
|
prev uint64
|
||||||
}
|
}
|
||||||
storageChange struct {
|
storageChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
key, prevalue common.Hash
|
key common.Hash
|
||||||
|
prevValue common.Hash
|
||||||
|
prevDirty bool
|
||||||
}
|
}
|
||||||
codeChange struct {
|
codeChange struct {
|
||||||
account *common.Address
|
account *common.Address
|
||||||
|
|
@ -196,7 +198,7 @@ func (ch codeChange) dirtied() *common.Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) revert(s *StateDB) {
|
func (ch storageChange) revert(s *StateDB) {
|
||||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
s.getStateObject(*ch.account).setState(ch.key, ch.prevValue, ch.prevDirty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch storageChange) dirtied() *common.Address {
|
func (ch storageChange) dirtied() *common.Address {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,6 @@ type stateObject struct {
|
||||||
code Code // contract bytecode, which gets set when code is loaded
|
code Code // contract bytecode, which gets set when code is loaded
|
||||||
|
|
||||||
originStorage Storage // Storage cache of original entries to dedup rewrites
|
originStorage Storage // Storage cache of original entries to dedup rewrites
|
||||||
cachedStorage Storage // Storage entry cache to avoid duplicate reads
|
|
||||||
dirtyStorage Storage // Storage entries that need to be flushed to disk
|
dirtyStorage Storage // Storage entries that need to be flushed to disk
|
||||||
|
|
||||||
// Cache flags.
|
// Cache flags.
|
||||||
|
|
@ -117,7 +116,6 @@ func newObject(db *StateDB, address common.Address, data Account) *stateObject {
|
||||||
addrHash: crypto.Keccak256Hash(address[:]),
|
addrHash: crypto.Keccak256Hash(address[:]),
|
||||||
data: data,
|
data: data,
|
||||||
originStorage: make(Storage),
|
originStorage: make(Storage),
|
||||||
cachedStorage: make(Storage),
|
|
||||||
dirtyStorage: make(Storage),
|
dirtyStorage: make(Storage),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,17 +159,22 @@ func (c *stateObject) getTrie(db Database) Trie {
|
||||||
return c.trie
|
return c.trie
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState returns a value in account storage.
|
// GetState returns a value from account storage, and also whether the slot is
|
||||||
func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
|
// dirty in the current transaction execution context.
|
||||||
value, exists := self.cachedStorage[key]
|
func (self *stateObject) GetState(db Database, key common.Hash) (common.Hash, bool) {
|
||||||
if exists {
|
value, dirty := self.dirtyStorage[key]
|
||||||
return value
|
if dirty {
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
value, cached := self.originStorage[key]
|
||||||
|
if cached {
|
||||||
|
return value, false
|
||||||
}
|
}
|
||||||
// Load from DB in case it is missing.
|
// Load from DB in case it is missing.
|
||||||
enc, err := self.getTrie(db).TryGet(key[:])
|
enc, err := self.getTrie(db).TryGet(key[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
self.setError(err)
|
self.setError(err)
|
||||||
return common.Hash{}
|
return common.Hash{}, false
|
||||||
}
|
}
|
||||||
if len(enc) > 0 {
|
if len(enc) > 0 {
|
||||||
_, content, _, err := rlp.Split(enc)
|
_, content, _, err := rlp.Split(enc)
|
||||||
|
|
@ -181,23 +184,33 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
|
||||||
value.SetBytes(content)
|
value.SetBytes(content)
|
||||||
}
|
}
|
||||||
self.originStorage[key] = value
|
self.originStorage[key] = value
|
||||||
self.cachedStorage[key] = value
|
return value, false
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetState updates a value in account storage.
|
// SetState updates a value in account storage.
|
||||||
func (self *stateObject) SetState(db Database, key, value common.Hash) {
|
func (self *stateObject) SetState(db Database, key, value common.Hash) {
|
||||||
|
// If the new value is the same as old, don't set and don't mark dirty
|
||||||
|
prev, dirty := self.GetState(db, key)
|
||||||
|
if prev == value {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// New value is different, update and journal the change
|
||||||
|
self.setState(key, value, true)
|
||||||
|
|
||||||
self.db.journal.append(storageChange{
|
self.db.journal.append(storageChange{
|
||||||
account: &self.address,
|
account: &self.address,
|
||||||
key: key,
|
key: key,
|
||||||
prevalue: self.GetState(db, key),
|
prevValue: prev,
|
||||||
|
prevDirty: dirty,
|
||||||
})
|
})
|
||||||
self.setState(key, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *stateObject) setState(key, value common.Hash) {
|
func (self *stateObject) setState(key, value common.Hash, dirty bool) {
|
||||||
self.cachedStorage[key] = value
|
if dirty {
|
||||||
self.dirtyStorage[key] = value
|
self.dirtyStorage[key] = value
|
||||||
|
} else {
|
||||||
|
delete(self.dirtyStorage, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateTrie writes cached storage modifications into the object's storage trie.
|
// updateTrie writes cached storage modifications into the object's storage trie.
|
||||||
|
|
@ -289,7 +302,6 @@ func (self *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
}
|
}
|
||||||
stateObject.code = self.code
|
stateObject.code = self.code
|
||||||
stateObject.dirtyStorage = self.dirtyStorage.Copy()
|
stateObject.dirtyStorage = self.dirtyStorage.Copy()
|
||||||
stateObject.cachedStorage = self.dirtyStorage.Copy()
|
|
||||||
stateObject.originStorage = self.originStorage.Copy()
|
stateObject.originStorage = self.originStorage.Copy()
|
||||||
stateObject.suicided = self.suicided
|
stateObject.suicided = self.suicided
|
||||||
stateObject.dirtyCode = self.dirtyCode
|
stateObject.dirtyCode = self.dirtyCode
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,17 @@ func (s *StateSuite) TestNull(c *checker.C) {
|
||||||
s.state.CreateAccount(address)
|
s.state.CreateAccount(address)
|
||||||
//value := common.FromHex("0x823140710bf13990e4500136726d8b55")
|
//value := common.FromHex("0x823140710bf13990e4500136726d8b55")
|
||||||
var value common.Hash
|
var value common.Hash
|
||||||
|
|
||||||
s.state.SetState(address, common.Hash{}, value)
|
s.state.SetState(address, common.Hash{}, value)
|
||||||
s.state.Commit(false)
|
s.state.Commit(false)
|
||||||
value = s.state.GetState(address, common.Hash{})
|
|
||||||
|
value, dirty := s.state.GetState(address, common.Hash{})
|
||||||
if value != (common.Hash{}) {
|
if value != (common.Hash{}) {
|
||||||
c.Errorf("expected empty hash. got %x", value)
|
c.Errorf("expected empty hash. got %x", value)
|
||||||
}
|
}
|
||||||
|
if dirty {
|
||||||
|
c.Errorf("expected non-dirty, got dirty")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateSuite) TestSnapshot(c *checker.C) {
|
func (s *StateSuite) TestSnapshot(c *checker.C) {
|
||||||
|
|
@ -110,20 +115,27 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
|
||||||
data1 := common.BytesToHash([]byte{42})
|
data1 := common.BytesToHash([]byte{42})
|
||||||
data2 := common.BytesToHash([]byte{43})
|
data2 := common.BytesToHash([]byte{43})
|
||||||
|
|
||||||
// set initial state object value
|
// snapshot the genesis state
|
||||||
|
genesis := s.state.Snapshot()
|
||||||
|
|
||||||
|
// set initial state object value and snapshot it
|
||||||
s.state.SetState(stateobjaddr, storageaddr, data1)
|
s.state.SetState(stateobjaddr, storageaddr, data1)
|
||||||
// get snapshot of current state
|
|
||||||
snapshot := s.state.Snapshot()
|
snapshot := s.state.Snapshot()
|
||||||
|
|
||||||
// set new state object value
|
// set a new state object value, revert it and ensure correct content
|
||||||
s.state.SetState(stateobjaddr, storageaddr, data2)
|
s.state.SetState(stateobjaddr, storageaddr, data2)
|
||||||
// restore snapshot
|
|
||||||
s.state.RevertToSnapshot(snapshot)
|
s.state.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
// get state storage value
|
value, dirty := s.state.GetState(stateobjaddr, storageaddr)
|
||||||
res := s.state.GetState(stateobjaddr, storageaddr)
|
c.Assert(data1, checker.DeepEquals, value)
|
||||||
|
c.Assert(true, checker.DeepEquals, dirty)
|
||||||
|
|
||||||
c.Assert(data1, checker.DeepEquals, res)
|
// revert up to the genesis state and ensure correct content
|
||||||
|
s.state.RevertToSnapshot(genesis)
|
||||||
|
|
||||||
|
value, dirty = s.state.GetState(stateobjaddr, storageaddr)
|
||||||
|
c.Assert(common.Hash{}, checker.DeepEquals, value)
|
||||||
|
c.Assert(false, checker.DeepEquals, dirty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
|
func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
|
||||||
|
|
@ -208,17 +220,30 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
|
||||||
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
|
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(so1.cachedStorage) != len(so0.cachedStorage) {
|
if len(so1.dirtyStorage) != len(so0.dirtyStorage) {
|
||||||
t.Errorf("Storage size mismatch: have %d, want %d", len(so1.cachedStorage), len(so0.cachedStorage))
|
t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage))
|
||||||
}
|
}
|
||||||
for k, v := range so1.cachedStorage {
|
for k, v := range so1.dirtyStorage {
|
||||||
if so0.cachedStorage[k] != v {
|
if so0.dirtyStorage[k] != v {
|
||||||
t.Errorf("Storage key %x mismatch: have %v, want %v", k, so0.cachedStorage[k], v)
|
t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for k, v := range so0.cachedStorage {
|
for k, v := range so0.dirtyStorage {
|
||||||
if so1.cachedStorage[k] != v {
|
if so1.dirtyStorage[k] != v {
|
||||||
t.Errorf("Storage key %x mismatch: have %v, want none.", k, v)
|
t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(so1.originStorage) != len(so0.originStorage) {
|
||||||
|
t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage))
|
||||||
|
}
|
||||||
|
for k, v := range so1.originStorage {
|
||||||
|
if so0.originStorage[k] != v {
|
||||||
|
t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range so0.originStorage {
|
||||||
|
if so1.originStorage[k] != v {
|
||||||
|
t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
@ -236,12 +237,12 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||||
return common.BytesToHash(stateObject.CodeHash())
|
return common.BytesToHash(stateObject.CodeHash())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *StateDB) GetState(addr common.Address, bhash common.Hash) common.Hash {
|
func (self *StateDB) GetState(addr common.Address, bhash common.Hash) (common.Hash, bool) {
|
||||||
stateObject := self.getStateObject(addr)
|
stateObject := self.getStateObject(addr)
|
||||||
if stateObject != nil {
|
if stateObject != nil {
|
||||||
return stateObject.GetState(self.db, bhash)
|
return stateObject.GetState(self.db, bhash)
|
||||||
}
|
}
|
||||||
return common.Hash{}
|
return common.Hash{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database retrieves the low level database supporting the lower level trie ops.
|
// Database retrieves the low level database supporting the lower level trie ops.
|
||||||
|
|
@ -430,24 +431,19 @@ func (self *StateDB) CreateAccount(addr common.Address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) {
|
func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash, dirty bool) bool) {
|
||||||
so := db.getStateObject(addr)
|
so := db.getStateObject(addr)
|
||||||
if so == nil {
|
if so == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// When iterating over the storage check the cache first
|
|
||||||
for h, value := range so.cachedStorage {
|
|
||||||
cb(h, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil))
|
it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil))
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
// ignore cached values
|
|
||||||
key := common.BytesToHash(db.trie.GetKey(it.Key))
|
key := common.BytesToHash(db.trie.GetKey(it.Key))
|
||||||
if _, ok := so.cachedStorage[key]; !ok {
|
if value, dirty := so.dirtyStorage[key]; dirty {
|
||||||
cb(key, common.BytesToHash(it.Value))
|
cb(key, value, true)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
cb(key, common.BytesToHash(it.Value), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,9 +520,36 @@ func (self *StateDB) RevertToSnapshot(revid int) {
|
||||||
self.validRevisions = self.validRevisions[:idx]
|
self.validRevisions = self.validRevisions[:idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRefund returns the current value of the refund counter.
|
// GetRefund returns the current value of the refund counter and any out-of-band
|
||||||
func (self *StateDB) GetRefund() uint64 {
|
// refunds at the end of the transaction.
|
||||||
return self.refund
|
func (self *StateDB) GetRefund(netSstoreMeter bool) uint64 {
|
||||||
|
// If legacy gas metering is used, return the current refund counter
|
||||||
|
refund := self.refund
|
||||||
|
if !netSstoreMeter {
|
||||||
|
return refund
|
||||||
|
}
|
||||||
|
// If we're already using the Constantinople net sstore gas metering, refund noops
|
||||||
|
for addr := range self.journal.dirties {
|
||||||
|
object := self.stateObjects[addr]
|
||||||
|
for key, value := range object.dirtyStorage {
|
||||||
|
// If the slot didn't change in the end, refund most gas
|
||||||
|
if value == object.originStorage[key] {
|
||||||
|
if value == (common.Hash{}) {
|
||||||
|
refund += params.NetSstoreRefundNoopZero
|
||||||
|
} else {
|
||||||
|
refund += params.NetSstoreRefundNoopNonZero
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the slot did change, but was set to zero, refund
|
||||||
|
if value == (common.Hash{}) {
|
||||||
|
refund += params.NetSstoreRefund
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Sorry, no refund for this slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return refund
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalise finalises the state by removing the self destructed objects
|
// Finalise finalises the state by removing the self destructed objects
|
||||||
|
|
|
||||||
|
|
@ -381,11 +381,19 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
|
checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
|
||||||
// Check storage.
|
// Check storage.
|
||||||
if obj := state.getStateObject(addr); obj != nil {
|
if obj := state.getStateObject(addr); obj != nil {
|
||||||
state.ForEachStorage(addr, func(key, val common.Hash) bool {
|
state.ForEachStorage(addr, func(key, value common.Hash, dirty bool) bool {
|
||||||
return checkeq("GetState("+key.Hex()+")", val, checkstate.GetState(addr, key))
|
v, d := checkstate.GetState(addr, key)
|
||||||
|
if ok := checkeq("GetState("+key.Hex()+").value", value, v); ok {
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
return checkeq("GetState("+key.Hex()+").dirty", dirty, d)
|
||||||
})
|
})
|
||||||
checkstate.ForEachStorage(addr, func(key, checkval common.Hash) bool {
|
checkstate.ForEachStorage(addr, func(key, value common.Hash, dirty bool) bool {
|
||||||
return checkeq("GetState("+key.Hex()+")", state.GetState(addr, key), checkval)
|
v, d := state.GetState(addr, key)
|
||||||
|
if ok := checkeq("GetState("+key.Hex()+").value", v, value); ok {
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
return checkeq("GetState("+key.Hex()+").dirty", d, dirty)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -393,9 +401,11 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.GetRefund() != checkstate.GetRefund() {
|
for _, netGasMetering := range []bool{false, true} {
|
||||||
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
|
if state.GetRefund(netGasMetering) != checkstate.GetRefund(netGasMetering) {
|
||||||
state.GetRefund(), checkstate.GetRefund())
|
return fmt.Errorf("got GetRefund(%v) == %d, want GetRefund(%v) == %d",
|
||||||
|
netGasMetering, state.GetRefund(netGasMetering), netGasMetering, checkstate.GetRefund(netGasMetering))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) {
|
if !reflect.DeepEqual(state.GetLogs(common.Hash{}), checkstate.GetLogs(common.Hash{})) {
|
||||||
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
||||||
|
|
|
||||||
|
|
@ -229,9 +229,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
|
||||||
|
|
||||||
func (st *StateTransition) refundGas() {
|
func (st *StateTransition) refundGas() {
|
||||||
// Apply refund counter, capped to half of the used gas.
|
// Apply refund counter, capped to half of the used gas.
|
||||||
refund := st.gasUsed() / 2
|
netSstoreMeter := st.evm.ChainConfig().IsConstantinople(st.evm.BlockNumber)
|
||||||
if refund > st.state.GetRefund() {
|
|
||||||
refund = st.state.GetRefund()
|
refund := st.state.GetRefund(netSstoreMeter)
|
||||||
|
if refund > st.gasUsed()/2 {
|
||||||
|
refund = st.gasUsed() / 2
|
||||||
}
|
}
|
||||||
st.gas += refund
|
st.gas += refund
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,23 +117,51 @@ 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) {
|
func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
var (
|
var (
|
||||||
y, x = stack.Back(1), stack.Back(0)
|
y, x = stack.Back(1), stack.Back(0)
|
||||||
val = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
|
value, dirty = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
|
||||||
)
|
)
|
||||||
// This checks for 3 scenario's and calculates gas accordingly
|
// The legacy gas metering only takes into consideration the current state
|
||||||
// 1. From a zero-value address to a non-zero value (NEW VALUE)
|
if !evm.chainRules.IsConstantinople {
|
||||||
// 2. From a non-zero value address to a zero-value address (DELETE)
|
// This checks for 3 scenario's and calculates gas accordingly
|
||||||
// 3. From a non-zero to a non-zero (CHANGE)
|
// 1. From a zero-value address to a non-zero value (NEW VALUE)
|
||||||
if val == (common.Hash{}) && y.Sign() != 0 {
|
// 2. From a non-zero value address to a zero-value address (DELETE)
|
||||||
// 0 => non 0
|
// 3. From a non-zero to a non-zero (CHANGE)
|
||||||
return params.SstoreSetGas, nil
|
switch {
|
||||||
} else if val != (common.Hash{}) && y.Sign() == 0 {
|
case value == (common.Hash{}) && y.Sign() != 0: // 0 => non 0
|
||||||
// non 0 => 0
|
return params.SstoreSetGas, nil
|
||||||
evm.StateDB.AddRefund(params.SstoreRefundGas)
|
case value != (common.Hash{}) && y.Sign() == 0: // non 0 => 0
|
||||||
return params.SstoreClearGas, nil
|
evm.StateDB.AddRefund(params.SstoreRefundGas)
|
||||||
} else {
|
return params.SstoreClearGas, nil
|
||||||
// non 0 => non 0 (or 0 => 0)
|
default: // non 0 => non 0 (or 0 => 0)
|
||||||
return params.SstoreResetGas, nil
|
return params.SstoreResetGas, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The new gas metering is based on net gas costs (EIP-1087)
|
||||||
|
//
|
||||||
|
// 0. A ‘dirty map’ for each transaction is maintained, tracking all storage
|
||||||
|
// slots in all contracts that have been modified in the current transaction.
|
||||||
|
// The dirty map is scoped in the same manner as updates to storage, meaning
|
||||||
|
// that changes to the dirty map in a call that later reverts are not retained.
|
||||||
|
// 1. When a storage slot is written to with the value it already contains, 200
|
||||||
|
// gas is deducted, but is not explicitly marked dirty.
|
||||||
|
// 2. When a storage slot’s value is changed for the first time, the slot is
|
||||||
|
// marked as dirty. If the slot was previously set to 0, 20000 gas is deducted;
|
||||||
|
// otherwise, 5000 gas is deducted.
|
||||||
|
// 2. When a storage slot that is already in the dirty map is written to, 200
|
||||||
|
// gas is deducted.
|
||||||
|
// 3. At the end of the transaction, for each slot in the dirty map:
|
||||||
|
// * If the slot was 0 before the transaction and is 0 now, refund 19800 gas.
|
||||||
|
// * If the slot was nonzero before the transaction and its value has not changed, refund 4800 gas.
|
||||||
|
// * If the slot was nonzero before the transaction and is 0 now, refund 10000 gas.
|
||||||
|
switch {
|
||||||
|
case value == common.BigToHash(y): // noop
|
||||||
|
return params.NetSstoreNoopGas, nil
|
||||||
|
case !dirty && y.Sign() != 0 && value == (common.Hash{}): // clean(0) => anything
|
||||||
|
return params.NetSstoreCleanInitgas, nil
|
||||||
|
case !dirty && y.Sign() != 0: // clean(non 0) => anything
|
||||||
|
return params.NetSstoreCleanUpdateGas, nil
|
||||||
|
default: // dirty(anything) => anything
|
||||||
|
return params.NetSstoreDirtyGas, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -604,7 +604,7 @@ func opMstore8(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo
|
||||||
|
|
||||||
func opSload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opSload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
loc := stack.peek()
|
loc := stack.peek()
|
||||||
val := interpreter.evm.StateDB.GetState(contract.Address(), common.BigToHash(loc))
|
val, _ := interpreter.evm.StateDB.GetState(contract.Address(), common.BigToHash(loc))
|
||||||
loc.SetBytes(val.Bytes())
|
loc.SetBytes(val.Bytes())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ type StateDB interface {
|
||||||
GetCodeSize(common.Address) int
|
GetCodeSize(common.Address) int
|
||||||
|
|
||||||
AddRefund(uint64)
|
AddRefund(uint64)
|
||||||
GetRefund() uint64
|
GetRefund(bool) uint64
|
||||||
|
|
||||||
GetState(common.Address, common.Hash) common.Hash
|
GetState(common.Address, common.Hash) (common.Hash, bool)
|
||||||
SetState(common.Address, common.Hash, common.Hash)
|
SetState(common.Address, common.Hash, common.Hash)
|
||||||
|
|
||||||
Suicide(common.Address) bool
|
Suicide(common.Address) bool
|
||||||
|
|
@ -61,7 +61,7 @@ type StateDB interface {
|
||||||
AddLog(*types.Log)
|
AddLog(*types.Log)
|
||||||
AddPreimage(common.Hash, []byte)
|
AddPreimage(common.Hash, []byte)
|
||||||
|
|
||||||
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool)
|
ForEachStorage(common.Address, func(common.Hash, common.Hash, bool) bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM EVM
|
// CallContext provides a basic interface for the EVM calling conventions. The EVM EVM
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err
|
||||||
in.intPool = nil
|
in.intPool = nil
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment the call depth which is restricted to 1024
|
// Increment the call depth which is restricted to 1024
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ func (dw *dbWrapper) pushObject(vm *duktape.Context) {
|
||||||
hash := popSlice(ctx)
|
hash := popSlice(ctx)
|
||||||
addr := popSlice(ctx)
|
addr := popSlice(ctx)
|
||||||
|
|
||||||
state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash))
|
state, _ := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash))
|
||||||
|
|
||||||
ptr := ctx.PushFixedBuffer(len(state))
|
ptr := ctx.PushFixedBuffer(len(state))
|
||||||
copy(makeSlice(ptr, uint(len(state))), state[:])
|
copy(makeSlice(ptr, uint(len(state))), state[:])
|
||||||
|
|
|
||||||
|
|
@ -597,7 +597,7 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A
|
||||||
if state == nil || err != nil {
|
if state == nil || err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res := state.GetState(address, common.HexToHash(key))
|
res, _ := state.GetState(address, common.HexToHash(key))
|
||||||
return res[:], state.Error()
|
return res[:], state.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ func (err *ConfigCompatError) Error() string {
|
||||||
type Rules struct {
|
type Rules struct {
|
||||||
ChainID *big.Int
|
ChainID *big.Int
|
||||||
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
|
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
|
||||||
IsByzantium bool
|
IsByzantium, IsConstantinople bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rules ensures c's ChainID is not nil.
|
// Rules ensures c's ChainID is not nil.
|
||||||
|
|
@ -343,5 +343,12 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
chainID = new(big.Int)
|
chainID = new(big.Int)
|
||||||
}
|
}
|
||||||
return Rules{ChainID: new(big.Int).Set(chainID), IsHomestead: c.IsHomestead(num), IsEIP150: c.IsEIP150(num), IsEIP155: c.IsEIP155(num), IsEIP158: c.IsEIP158(num), IsByzantium: c.IsByzantium(num)}
|
return Rules{
|
||||||
|
ChainID: new(big.Int).Set(chainID),
|
||||||
|
IsHomestead: c.IsHomestead(num),
|
||||||
|
IsEIP150: c.IsEIP150(num),
|
||||||
|
IsEIP155: c.IsEIP155(num),
|
||||||
|
IsEIP158: c.IsEIP158(num),
|
||||||
|
IsByzantium: c.IsByzantium(num),
|
||||||
|
IsConstantinople: c.IsConstantinople(num)}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,25 @@ const (
|
||||||
TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions.
|
TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions.
|
||||||
TxDataZeroGas uint64 = 4 // Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions.
|
TxDataZeroGas uint64 = 4 // Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions.
|
||||||
QuadCoeffDiv uint64 = 512 // Divisor for the quadratic particle of the memory cost equation.
|
QuadCoeffDiv uint64 = 512 // Divisor for the quadratic particle of the memory cost equation.
|
||||||
SstoreSetGas uint64 = 20000 // Once per SLOAD operation.
|
|
||||||
LogDataGas uint64 = 8 // Per byte in a LOG* operation's data.
|
LogDataGas uint64 = 8 // Per byte in a LOG* operation's data.
|
||||||
CallStipend uint64 = 2300 // Free gas given at beginning of call.
|
CallStipend uint64 = 2300 // Free gas given at beginning of call.
|
||||||
|
|
||||||
Sha3Gas uint64 = 30 // Once per SHA3 operation.
|
Sha3Gas uint64 = 30 // Once per SHA3 operation.
|
||||||
Sha3WordGas uint64 = 6 // Once per word of the SHA3 operation's data.
|
Sha3WordGas uint64 = 6 // Once per word of the SHA3 operation's data.
|
||||||
SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero.
|
|
||||||
SstoreClearGas uint64 = 5000 // Once per SSTORE operation if the zeroness doesn't change.
|
SstoreSetGas uint64 = 20000 // Once per SSTORE operation.
|
||||||
SstoreRefundGas uint64 = 15000 // Once per SSTORE operation if the zeroness changes to zero.
|
SstoreResetGas uint64 = 5000 // Once per SSTORE operation if the zeroness changes from zero.
|
||||||
|
SstoreClearGas uint64 = 5000 // Once per SSTORE operation if the zeroness doesn't change.
|
||||||
|
SstoreRefundGas uint64 = 15000 // Once per SSTORE operation if the zeroness changes to zero.
|
||||||
|
|
||||||
|
NetSstoreNoopGas uint64 = 200 // Once per SSTORE operation if the value doesn't change.
|
||||||
|
NetSstoreDirtyGas uint64 = 200 // Once per SSTORE operation from dirty.
|
||||||
|
NetSstoreCleanInitgas uint64 = 20000 // Once per SSTORE operation from clean zero.
|
||||||
|
NetSstoreCleanUpdateGas uint64 = 5000 // Once per SSTORE operation from clean non-zero.
|
||||||
|
NetSstoreRefundNoopZero uint64 = 19800 // Once per dirty slot if it was changed from zero to non-zero and back to zero.
|
||||||
|
NetSstoreRefundNoopNonZero uint64 = 4800 // Once per dirty slot if it was changed from non-zero to anything and back to the orignal.
|
||||||
|
NetSstoreRefund uint64 = 10000 // Once per dirty slot if it was changed from non-zero to zero.
|
||||||
|
|
||||||
JumpdestGas uint64 = 1 // Refunded gas, once per SSTORE operation if the zeroness changes to zero.
|
JumpdestGas uint64 = 1 // Refunded gas, once per SSTORE operation if the zeroness changes to zero.
|
||||||
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
|
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
|
||||||
CallGas uint64 = 40 // Once per CALL operation & message call transaction.
|
CallGas uint64 = 40 // Once per CALL operation & message call transaction.
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func (t *VMTest) Run(vmconfig vm.Config) error {
|
||||||
}
|
}
|
||||||
for addr, account := range t.json.Post {
|
for addr, account := range t.json.Post {
|
||||||
for k, wantV := range account.Storage {
|
for k, wantV := range account.Storage {
|
||||||
if haveV := statedb.GetState(addr, k); haveV != wantV {
|
if haveV, _ := statedb.GetState(addr, k); haveV != wantV {
|
||||||
return fmt.Errorf("wrong storage value at %x:\n got %x\n want %x", k, haveV, wantV)
|
return fmt.Errorf("wrong storage value at %x:\n got %x\n want %x", k, haveV, wantV)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue