diff --git a/libethcore/EVMSchedule.h b/libethcore/EVMSchedule.h index 3a658c5f152..ea3bab55aa9 100644 --- a/libethcore/EVMSchedule.h +++ b/libethcore/EVMSchedule.h @@ -33,6 +33,7 @@ struct EVMSchedule bool haveDelegateCall = true; bool eip150Mode = false; bool eip158Mode = false; + bool eip1283Mode = false; bool haveBitwiseShifting = false; bool haveRevert = false; bool haveReturnData = false; @@ -47,7 +48,9 @@ struct EVMSchedule unsigned sloadGas = 50; unsigned sstoreSetGas = 20000; unsigned sstoreResetGas = 5000; + unsigned sstoreUnchangedGas = 200; unsigned sstoreRefundGas = 15000; + unsigned sstoreRefundNonzeroGas = 4800; unsigned jumpdestGas = 1; unsigned logGas = 375; unsigned logDataGas = 8; @@ -135,6 +138,7 @@ static const EVMSchedule ConstantinopleSchedule = [] schedule.haveCreate2 = true; schedule.haveBitwiseShifting = true; schedule.haveExtcodehash = true; + schedule.eip1283Mode = true; return schedule; }(); diff --git a/libethereum/Account.cpp b/libethereum/Account.cpp index 8d98cae820f..ba2b6af615d 100644 --- a/libethereum/Account.cpp +++ b/libethereum/Account.cpp @@ -20,8 +20,10 @@ */ #include "Account.h" +#include "SecureTrieDB.h" #include "ValidationSchemes.h" #include +#include #include #include @@ -39,6 +41,20 @@ void Account::setCode(bytes&& _code) m_codeHash = sha3(m_codeCache); } +u256 Account::originalStorageValue(u256 const& _key, OverlayDB const& _db) const +{ + auto it = m_storageOriginal.find(_key); + if (it != m_storageOriginal.end()) + return it->second; + + // Not in the original values cache - go to the DB. + SecureTrieDB const memdb(const_cast(&_db), m_storageRoot); + std::string const payload = memdb.at(_key); + auto const value = payload.size() ? RLP(payload).toInt() : 0; + m_storageOriginal[_key] = value; + return value; +} + namespace js = json_spirit; namespace diff --git a/libethereum/Account.h b/libethereum/Account.h index ecea5d23309..3f6208c84a9 100644 --- a/libethereum/Account.h +++ b/libethereum/Account.h @@ -22,13 +22,16 @@ #pragma once #include -#include -#include #include +#include #include +#include + namespace dev { +class OverlayDB; + namespace eth { @@ -45,20 +48,6 @@ namespace eth * in the overlay, stored in this class and retrieved with storageOverlay(). setStorage allows the overlay * to be altered. * - * The code handling explicitly supports a two-stage commit model needed for contract-creation. When creating - * a contract (running the initialisation code), the code of the account is considered empty. The attribute - * of emptiness can be retrieved with codeBearing(). After initialisation one must set the code accordingly; - * the code of the Account can be set with setCode(). To validate a setCode() call, this class records the - * state of being in contract-creation (and thus in a state where setCode may validly be called). It can be - * determined through isFreshCode(). - * - * The code can be retrieved through code(), and its hash through codeHash(). codeHash() is only valid when - * the account is not in the contract-creation phase (i.e. when isFreshCode() returns false). This class - * supports populating code on-demand from the state database. To determine if the code has been prepopulated - * call codeCacheValid(). To populate the code, look it up with codeHash() and populate with noteCode(). - * - * @todo: need to make a noteCodeCommitted(). - * * The constructor allows you to create an one of a number of "types" of accounts. The default constructor * makes a dead account (this is ignored by State when writing out the Trie). Another three allow a basic * or contract account to be specified along with an initial balance. The fina two allow either a basic or @@ -88,8 +77,19 @@ class Account Account(u256 _nonce, u256 _balance, h256 _contractRoot, h256 _codeHash, Changedness _c): m_isAlive(true), m_isUnchanged(_c == Unchanged), m_nonce(_nonce), m_balance(_balance), m_storageRoot(_contractRoot), m_codeHash(_codeHash) { assert(_contractRoot); } - /// Kill this account. Useful for the suicide opcode. Following this call, isAlive() returns false. - void kill() { m_isAlive = false; m_storageOverlay.clear(); m_codeHash = EmptySHA3; m_storageRoot = EmptyTrie; m_balance = 0; m_nonce = 0; changed(); } + /// Kill this account. Useful for the suicide opcode. Following this call, isAlive() returns + /// false. + void kill() + { + m_isAlive = false; + m_storageOverlay.clear(); + m_storageOriginal.clear(); + m_codeHash = EmptySHA3; + m_storageRoot = EmptyTrie; + m_balance = 0; + m_nonce = 0; + changed(); + } /// @returns true iff this object represents an account in the state. Returns false if this object /// represents an account that should no longer exist in the trie (an account that never existed or was @@ -121,11 +121,25 @@ class Account /// the account. void setNonce(u256 const& _nonce) { m_nonce = _nonce; changed(); } - /// @returns the root of the trie (whose nodes are stored in the state db externally to this class) /// which encodes the base-state of the account's storage (upon which the storage is overlaid). h256 baseRoot() const { assert(m_storageRoot); return m_storageRoot; } + /// @returns account's storage value corresponding to the @_key + /// taking into account overlayed modifications + u256 storageValue(u256 const& _key, OverlayDB const& _db) const + { + auto mit = m_storageOverlay.find(_key); + if (mit != m_storageOverlay.end()) + return mit->second; + + return originalStorageValue(_key, _db); + } + + /// @returns account's original storage value corresponding to the @_key + /// not taking into account overlayed modifications + u256 originalStorageValue(u256 const& _key, OverlayDB const& _db) const; + /// @returns the storage overlay as a simple hash map. std::unordered_map const& storageOverlay() const { return m_storageOverlay; } @@ -139,10 +153,6 @@ class Account /// Set the storage root. Used when clearStorage() is reverted. void setStorageRoot(h256 const& _root) { m_storageOverlay.clear(); m_storageRoot = _root; changed(); } - /// Set a key/value pair in the account's storage to a value that is already present inside the - /// database. - void setStorageCache(u256 _p, u256 _v) const { const_cast(m_storageOverlay)[_p] = _v; } - /// @returns the hash of the account's code. h256 codeHash() const { return m_codeHash; } @@ -193,10 +203,13 @@ class Account h256 m_codeHash = EmptySHA3; /// The map with is overlaid onto whatever storage is implied by the m_storageRoot in the trie. - std::unordered_map m_storageOverlay; + mutable std::unordered_map m_storageOverlay; + + /// The cache of unmodifed storage items + mutable std::unordered_map m_storageOriginal; - /// The associated code for this account. The SHA3 of this should be equal to m_codeHash unless m_codeHash - /// equals c_contractConceptionCodeHash. + /// The associated code for this account. The SHA3 of this should be equal to m_codeHash unless + /// m_codeHash equals c_contractConceptionCodeHash. bytes m_codeCache; /// Value for m_codeHash when this account is having its code determined. @@ -249,7 +262,7 @@ class PrecompiledContract; using PrecompiledContractMap = std::unordered_map; AccountMap jsonToAccountMap(std::string const& _json, u256 const& _defaultNonce = 0, - AccountMaskMap* o_mask = nullptr, PrecompiledContractMap* o_precompiled = nullptr, - const boost::filesystem::path& _configPath = {}); + AccountMaskMap* o_mask = nullptr, PrecompiledContractMap* o_precompiled = nullptr, + const boost::filesystem::path& _configPath = {}); } } diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index bc07fba45ec..12940fad2de 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -60,6 +60,12 @@ class ExtVM : public ExtVMFace /// Write a value in storage. void setStore(u256 _n, u256 _v) final; + /// Read original storage value (before modifications in the current transaction). + u256 originalStorageValue(u256 const& _key) final + { + return m_s.originalStorageValue(myAddress, _key); + } + /// Read address's code. bytes const& codeAt(Address _a) final { return m_s.code(_a); } diff --git a/libethereum/SecureTrieDB.h b/libethereum/SecureTrieDB.h new file mode 100644 index 00000000000..4bbb2db4aac --- /dev/null +++ b/libethereum/SecureTrieDB.h @@ -0,0 +1,35 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ + +#pragma once + +#include + +namespace dev +{ +namespace eth +{ +#if ETH_FATDB +template +using SecureTrieDB = SpecificTrieDB, KeyType>; +#else +template +using SecureTrieDB = SpecificTrieDB, KeyType>; +#endif + +} // namespace eth +} // namespace dev diff --git a/libethereum/State.cpp b/libethereum/State.cpp index b14732a531c..b0f11bb5042 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -418,18 +418,7 @@ u256 State::getNonce(Address const& _addr) const u256 State::storage(Address const& _id, u256 const& _key) const { if (Account const* a = account(_id)) - { - auto mit = a->storageOverlay().find(_key); - if (mit != a->storageOverlay().end()) - return mit->second; - - // Not in the storage cache - go to the DB. - SecureTrieDB memdb(const_cast(&m_db), a->baseRoot()); // promise we won't change the overlay! :) - string payload = memdb.at(_key); - u256 ret = payload.size() ? RLP(payload).toInt() : 0; - a->setStorageCache(_key, ret); - return ret; - } + return a->storageValue(_key, m_db); else return 0; } @@ -440,6 +429,14 @@ void State::setStorage(Address const& _contract, u256 const& _key, u256 const& _ m_cache[_contract].setStorage(_key, _value); } +u256 State::originalStorageValue(Address const& _contract, u256 const& _key) const +{ + if (Account const* a = account(_contract)) + return a->originalStorageValue(_key, m_db); + else + return 0; +} + void State::clearStorage(Address const& _contract) { h256 const& oldHash{m_cache[_contract].baseRoot()}; diff --git a/libethereum/State.h b/libethereum/State.h index 7c01b5b1d71..671a2af04fa 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -17,20 +17,20 @@ #pragma once -#include -#include +#include "Account.h" +#include "GasPricer.h" +#include "SecureTrieDB.h" +#include "Transaction.h" +#include "TransactionReceipt.h" #include -#include -#include #include -#include +#include #include +#include #include #include -#include "Account.h" -#include "Transaction.h" -#include "TransactionReceipt.h" -#include "GasPricer.h" +#include +#include namespace dev { @@ -73,15 +73,9 @@ enum class Permanence { Reverted, Committed, - Uncommitted ///< Uncommitted state for change log readings in tests. + Uncommitted ///< Uncommitted state for change log readings in tests. }; -#if ETH_FATDB -template using SecureTrieDB = SpecificTrieDB, KeyType>; -#else -template using SecureTrieDB = SpecificTrieDB, KeyType>; -#endif - DEV_SIMPLE_EXCEPTION(InvalidAccountStartNonceInState); DEV_SIMPLE_EXCEPTION(IncorrectAccountStartNonceInState); @@ -266,6 +260,11 @@ class State /// Set the value of a storage position of an account. void setStorage(Address const& _contract, u256 const& _location, u256 const& _value); + /// Get the original value of a storage position of an account (before modifications saved in + /// account cache). + /// @returns 0 if no account exists at that address. + u256 originalStorageValue(Address const& _contract, u256 const& _key) const; + /// Clear the storage root hash of an account to the hash of the empty trie. void clearStorage(Address const& _contract); @@ -322,7 +321,7 @@ class State u256 const& requireAccountStartNonce() const; void noteAccountStartNonce(u256 const& _actual); - /// Create a savepoint in the state changelog. /// + /// Create a savepoint in the state changelog. /// @return The savepoint index that can be used in rollback() function. size_t savepoint() const; @@ -352,12 +351,19 @@ class State /// exception occurred. bool executeTransaction(Executive& _e, Transaction const& _t, OnOpFunc const& _onOp); - OverlayDB m_db; ///< Our overlay for the state tree. - SecureTrieDB m_state; ///< Our state tree, as an OverlayDB DB. - mutable std::unordered_map m_cache; ///< Our address cache. This stores the states of each address that has (or at least might have) been changed. - mutable std::vector
m_unchangedCacheEntries; ///< Tracks entries in m_cache that can potentially be purged if it grows too large. - mutable std::set
m_nonExistingAccountsCache; ///< Tracks addresses that are known to not exist. - AddressHash m_touched; ///< Tracks all addresses touched so far. + /// Our overlay for the state tree. + OverlayDB m_db; + /// Our state tree, as an OverlayDB DB. + SecureTrieDB m_state; + /// Our address cache. This stores the states of each address that has (or at least might have) + /// been changed. + mutable std::unordered_map m_cache; + /// Tracks entries in m_cache that can potentially be purged if it grows too large. + mutable std::vector
m_unchangedCacheEntries; + /// Tracks addresses that are known to not exist. + mutable std::set
m_nonExistingAccountsCache; + /// Tracks all addresses touched so far. + AddressHash m_touched; u256 m_accountStartNonce; diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index f920e91b3db..323a7708369 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -218,6 +218,9 @@ class ExtVMFace: public evmc_context /// Write a value in storage. virtual void setStore(u256, u256) {} + /// Read original storage value (before modifications in the current transaction). + virtual u256 originalStorageValue(u256 const&) { return 0; } + /// Read address's balance. virtual u256 balance(Address) { return 0; } diff --git a/libevm/LegacyVM.cpp b/libevm/LegacyVM.cpp index b8d6445616c..1231a25cf52 100644 --- a/libevm/LegacyVM.cpp +++ b/libevm/LegacyVM.cpp @@ -97,9 +97,20 @@ void LegacyVM::adjustStack(unsigned _removed, unsigned _added) void LegacyVM::updateSSGas() { - if (!m_ext->store(m_SP[0]) && m_SP[1]) + u256 const currentValue = m_ext->store(m_SP[0]); + u256 const newValue = m_SP[1]; + + if (m_schedule->eip1283Mode) + updateSSGasEIP1283(currentValue, newValue); + else + updateSSGasPreEIP1283(currentValue, newValue); +} + +void LegacyVM::updateSSGasPreEIP1283(u256 const& _currentValue, u256 const& _newValue) +{ + if (!_currentValue && _newValue) m_runGas = toInt63(m_schedule->sstoreSetGas); - else if (m_ext->store(m_SP[0]) && !m_SP[1]) + else if (_currentValue && !_newValue) { m_runGas = toInt63(m_schedule->sstoreResetGas); m_ext->sub.refunds += m_schedule->sstoreRefundGas; @@ -108,6 +119,49 @@ void LegacyVM::updateSSGas() m_runGas = toInt63(m_schedule->sstoreResetGas); } +void LegacyVM::updateSSGasEIP1283(u256 const& _currentValue, u256 const& _newValue) +{ + if (_currentValue == _newValue) + m_runGas = m_schedule->sstoreUnchangedGas; + else + { + u256 const originalValue = m_ext->originalStorageValue(m_SP[0]); + if (originalValue == _currentValue) + { + if (originalValue == 0) + m_runGas = m_schedule->sstoreSetGas; + else + { + m_runGas = m_schedule->sstoreResetGas; + if (_newValue == 0) + m_ext->sub.refunds += m_schedule->sstoreRefundGas; + } + } + else + { + m_runGas = m_schedule->sstoreUnchangedGas; + if (originalValue != 0) + { + if (_currentValue == 0) + { + assert(m_ext->sub.refunds >= m_schedule->sstoreRefundGas); + m_ext->sub.refunds -= m_schedule->sstoreRefundGas; + } + else + m_ext->sub.refunds += m_schedule->sstoreRefundGas; + } + if (originalValue == _newValue) + { + if (originalValue == 0) + m_ext->sub.refunds += + m_schedule->sstoreRefundGas + m_schedule->sstoreRefundNonzeroGas; + else + m_ext->sub.refunds += m_schedule->sstoreRefundNonzeroGas; + } + } + } +} + uint64_t LegacyVM::gasForMem(u512 _size) { diff --git a/libevm/LegacyVM.h b/libevm/LegacyVM.h index 782538d085a..081a58ffd1b 100644 --- a/libevm/LegacyVM.h +++ b/libevm/LegacyVM.h @@ -134,6 +134,8 @@ class LegacyVM: public VMFace void adjustStack(unsigned _removed, unsigned _added); uint64_t gasForMem(u512 _size); void updateSSGas(); + void updateSSGasPreEIP1283(u256 const& _currentValue, u256 const& _newValue); + void updateSSGasEIP1283(u256 const& _currentValue, u256 const& _newValue); void updateIOGas(); void updateGas(); void updateMem(uint64_t _newMem); diff --git a/test/unittests/libevm/VMTest.cpp b/test/unittests/libevm/VMTest.cpp index cbc4a2aa455..dd144435407 100644 --- a/test/unittests/libevm/VMTest.cpp +++ b/test/unittests/libevm/VMTest.cpp @@ -317,6 +317,91 @@ class AlethInterpreterExtcodehashTestFixture : public ExtcodehashTestFixture {} }; +class SstoreTestFixture : public TestOutputHelperFixture +{ +public: + explicit SstoreTestFixture(VMFace* _vm) : vm{_vm} + { + state.addBalance(from, 1 * ether); + state.addBalance(to, 1 * ether); + } + + void testEip1283Case1() { testGasConsumed("0x60006000556000600055", 0, 412); } + + void testEip1283Case2() { testGasConsumed("0x60006000556001600055", 0, 20212); } + + void testEip1283Case3() { testGasConsumed("0x60016000556000600055", 0, 20212); } + + void testEip1283Case4() { testGasConsumed("0x60016000556002600055", 0, 20212); } + + void testEip1283Case5() { testGasConsumed("0x60016000556001600055", 0, 20212); } + + void testEip1283Case6() { testGasConsumed("0x60006000556000600055", 1, 5212); } + + void testEip1283Case7() { testGasConsumed("0x60006000556001600055", 1, 5212); } + + void testEip1283Case8() { testGasConsumed("0x60006000556002600055", 1, 5212); } + + void testEip1283Case9() { testGasConsumed("0x60026000556000600055", 1, 5212); } + + void testEip1283Case10() { testGasConsumed("0x60026000556003600055", 1, 5212); } + + void testEip1283Case11() { testGasConsumed("0x60026000556001600055", 1, 5212); } + + void testEip1283Case12() { testGasConsumed("0x60026000556002600055", 1, 5212); } + + void testEip1283Case13() { testGasConsumed("0x60016000556000600055", 1, 5212); } + + void testEip1283Case14() { testGasConsumed("0x60016000556002600055", 1, 5212); } + + void testEip1283Case15() { testGasConsumed("0x60016000556001600055", 1, 412); } + + void testEip1283Case16() { testGasConsumed("0x600160005560006000556001600055", 0, 40218); } + + void testEip1283Case17() { testGasConsumed("0x600060005560016000556000600055", 1, 10218); } + + void testGasConsumed( + std::string const& _codeStr, u256 const& _originalValue, u256 const& _expectedGasConsumed) + { + state.setStorage(to, 0, _originalValue); + state.commit(State::CommitBehaviour::RemoveEmptyAccounts); + + bytes const code = fromHex(_codeStr); + ExtVM extVm(state, envInfo, *se, to, from, from, value, gasPrice, inputData, ref(code), + sha3(code), depth, isCreate, staticCall); + + u256 gasBefore = gas; + owning_bytes_ref ret = vm->exec(gas, extVm, OnOpFunc{}); + + BOOST_REQUIRE_EQUAL(gasBefore - gas, _expectedGasConsumed); + } + + + BlockHeader blockHeader{initBlockHeader()}; + LastBlockHashes lastBlockHashes; + EnvInfo envInfo{blockHeader, lastBlockHashes, 0}; + Address from{KeyPair::create().address()}; + Address to{KeyPair::create().address()}; + State state{0}; + std::unique_ptr se{ + ChainParams(genesisInfo(Network::ConstantinopleTest)).createSealEngine()}; + + u256 value = 0; + u256 gasPrice = 1; + int depth = 0; + bool isCreate = false; + bool staticCall = false; + u256 gas = 1000000; + bytesConstRef inputData; + + std::unique_ptr vm; +}; + +class LegacyVMSstoreTestFixture : public SstoreTestFixture +{ +public: + LegacyVMSstoreTestFixture() : SstoreTestFixture{new LegacyVM} {} +}; } // namespace @@ -392,6 +477,95 @@ BOOST_AUTO_TEST_CASE(LegacyVMExtcodehashIgnoresHigh12Bytes) testExtcodehashIgnoresHigh12Bytes(); } +BOOST_AUTO_TEST_SUITE_END() + +BOOST_FIXTURE_TEST_SUITE(LegacyVMSstoreSuite, LegacyVMSstoreTestFixture) + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case1) +{ + testEip1283Case1(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case2) +{ + testEip1283Case2(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case3) +{ + testEip1283Case3(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case4) +{ + testEip1283Case4(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case5) +{ + testEip1283Case5(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case6) +{ + testEip1283Case6(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case7) +{ + testEip1283Case7(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case8) +{ + testEip1283Case8(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case9) +{ + testEip1283Case9(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case10) +{ + testEip1283Case10(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case11) +{ + testEip1283Case11(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case12) +{ + testEip1283Case12(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case13) +{ + testEip1283Case13(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case14) +{ + testEip1283Case14(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case15) +{ + testEip1283Case15(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case16) +{ + testEip1283Case16(); +} + +BOOST_AUTO_TEST_CASE(LegacyVMSstoreEip1283Case17) +{ + testEip1283Case17(); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()