diff --git a/evmjit/CMakeLists.txt b/evmjit/CMakeLists.txt index a0e9c1f46..280fec212 100644 --- a/evmjit/CMakeLists.txt +++ b/evmjit/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_AUTOMOC OFF) if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") else() - set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra -Wconversion -Wno-sign-conversion -Wno-unknown-pragmas") + set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra -Wconversion -Wno-sign-conversion -Wno-unknown-pragmas ${CMAKE_CXX_FLAGS}") endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/evmjit/include/evmjit/JIT.h b/evmjit/include/evmjit/JIT.h index 446dd9e56..c9ddde705 100644 --- a/evmjit/include/evmjit/JIT.h +++ b/evmjit/include/evmjit/JIT.h @@ -28,8 +28,8 @@ public: private: friend class dev::eth::jit::ExecutionEngine; - static void* getCode(h256 _codeHash); - static void mapCode(h256 _codeHash, void* _funcAddr); + static uint64_t getCode(h256 _codeHash); + static void mapCode(h256 _codeHash, uint64_t _funcAddr); }; } diff --git a/evmjit/libevmjit/ExecutionEngine.cpp b/evmjit/libevmjit/ExecutionEngine.cpp index 08ca403b5..e5abb36b3 100644 --- a/evmjit/libevmjit/ExecutionEngine.cpp +++ b/evmjit/libevmjit/ExecutionEngine.cpp @@ -183,7 +183,7 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) entryFuncPtr = (EntryFuncPtr)ee->getFunctionAddress(mainFuncName); if (!CHECK(entryFuncPtr)) return ReturnCode::LLVMLinkError; - JIT::mapCode(_data->codeHash, (void*)entryFuncPtr); // FIXME: Remove cast + JIT::mapCode(_data->codeHash, (uint64_t)entryFuncPtr); // FIXME: Remove cast } listener->stateChanged(ExecState::Execution); diff --git a/evmjit/libevmjit/JIT.cpp b/evmjit/libevmjit/JIT.cpp index 9774c7396..8617fab6d 100644 --- a/evmjit/libevmjit/JIT.cpp +++ b/evmjit/libevmjit/JIT.cpp @@ -12,7 +12,7 @@ namespace class JITImpl: JIT { public: - std::unordered_map codeMap; + std::unordered_map codeMap; static JITImpl& instance() { @@ -28,16 +28,16 @@ bool JIT::isCodeReady(h256 _codeHash) return JITImpl::instance().codeMap.count(_codeHash) != 0; } -void* JIT::getCode(h256 _codeHash) +uint64_t JIT::getCode(h256 _codeHash) { auto& codeMap = JITImpl::instance().codeMap; auto it = codeMap.find(_codeHash); if (it != codeMap.end()) return it->second; - return nullptr; + return 0; } -void JIT::mapCode(h256 _codeHash, void* _funcAddr) +void JIT::mapCode(h256 _codeHash, uint64_t _funcAddr) { JITImpl::instance().codeMap.insert(std::make_pair(_codeHash, _funcAddr)); } diff --git a/libethash-cl/ethash_cl_miner.cpp b/libethash-cl/ethash_cl_miner.cpp index be17ba449..93ce9ab22 100644 --- a/libethash-cl/ethash_cl_miner.cpp +++ b/libethash-cl/ethash_cl_miner.cpp @@ -304,7 +304,7 @@ void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook }; std::queue pending; - static uint32_t const c_zero = 0; + uint32_t const c_zero = 0; // update header constant buffer m_queue.enqueueWriteBuffer(m_header, false, 0, 32, header); diff --git a/libevm/VM.cpp b/libevm/VM.cpp index 27650e3ad..ff771d21c 100644 --- a/libevm/VM.cpp +++ b/libevm/VM.cpp @@ -635,7 +635,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) if (m_stack[m_stack.size() - 3] > 0) callParams->gas += c_callStipend; m_stack.pop_back(); - callParams->receiveAddress = asAddress(m_stack.back()); + callParams->codeAddress = asAddress(m_stack.back()); m_stack.pop_back(); callParams->value = m_stack.back(); m_stack.pop_back(); @@ -653,7 +653,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) { callParams->onOp = _onOp; callParams->senderAddress = _ext.myAddress; - callParams->codeAddress = inst == Instruction::CALL ? callParams->receiveAddress : callParams->senderAddress; + callParams->receiveAddress = inst == Instruction::CALL ? callParams->codeAddress : callParams->senderAddress; callParams->data = bytesConstRef(m_temp.data() + inOff, inSize); callParams->out = bytesRef(m_temp.data() + outOff, outSize); m_stack.push_back(_ext.call(*callParams)); diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 6f38b0f42..5cf3b787a 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -431,6 +431,7 @@ bytes Assembly::assemble() const case PushSubSize: { auto s = m_data[i.data()].size(); + i.setPushedValue(u256(s)); byte b = max(1, dev::bytesRequired(s)); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index b3012a7ea..7d8f3d9a4 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -84,11 +84,17 @@ public: JumpType getJumpType() const { return m_jumpType; } std::string getJumpTypeAsString() const; + void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared(_value); } + u256 const* pushedValue() const { return m_pushedValue.get(); } + private: AssemblyItemType m_type; u256 m_data; SourceLocation m_location; JumpType m_jumpType = JumpType::Ordinary; + /// Pushed value for operations with data to be determined during assembly stage, + /// e.g. PushSubSize, PushTag, PushSub, etc. + mutable std::shared_ptr m_pushedValue; }; using AssemblyItems = std::vector; diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index e5fb0e09a..a8dc4dd58 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -20,6 +20,7 @@ */ #include "GasMeter.h" +#include #include using namespace std; @@ -41,55 +42,162 @@ GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption co GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item) { - switch (_item.type()) { + GasConsumption gas; + switch (_item.type()) + { case Push: case PushTag: - return runGas(Instruction::PUSH1); + case PushData: + case PushString: + case PushSub: + case PushSubSize: + case PushProgramSize: + gas = runGas(Instruction::PUSH1); + break; case Tag: - return runGas(Instruction::JUMPDEST); + gas = runGas(Instruction::JUMPDEST); + break; case Operation: { - GasConsumption gas = runGas(_item.instruction()); + ExpressionClasses& classes = m_state->expressionClasses(); + gas = runGas(_item.instruction()); switch (_item.instruction()) { case Instruction::SSTORE: - // @todo logic can be improved - gas += c_sstoreSetGas; + { + ExpressionClasses::Id slot = m_state->relativeStackElement(0); + ExpressionClasses::Id value = m_state->relativeStackElement(-1); + if (classes.knownZero(value) || ( + m_state->storageContent().count(slot) && + classes.knownNonZero(m_state->storageContent().at(slot)) + )) + gas += c_sstoreResetGas; //@todo take refunds into account + else + gas += c_sstoreSetGas; break; + } case Instruction::SLOAD: gas += c_sloadGas; break; + case Instruction::RETURN: + gas += memoryGas(0, -1); + break; + case Instruction::MLOAD: case Instruction::MSTORE: + gas += memoryGas(classes.find(eth::Instruction::ADD, { + m_state->relativeStackElement(0), + classes.find(AssemblyItem(32)) + })); + break; case Instruction::MSTORE8: - case Instruction::MLOAD: - case Instruction::RETURN: + gas += memoryGas(classes.find(eth::Instruction::ADD, { + m_state->relativeStackElement(0), + classes.find(AssemblyItem(1)) + })); + break; case Instruction::SHA3: + gas = c_sha3Gas; + gas += wordGas(c_sha3WordGas, m_state->relativeStackElement(-1)); + gas += memoryGas(0, -1); + break; case Instruction::CALLDATACOPY: case Instruction::CODECOPY: + gas += memoryGas(0, -2); + gas += wordGas(c_copyGas, m_state->relativeStackElement(-2)); + break; case Instruction::EXTCODECOPY: + gas += memoryGas(-1, -3); + gas += wordGas(c_copyGas, m_state->relativeStackElement(-3)); + break; case Instruction::LOG0: case Instruction::LOG1: case Instruction::LOG2: case Instruction::LOG3: case Instruction::LOG4: + { + unsigned n = unsigned(_item.instruction()) - unsigned(Instruction::LOG0); + gas = c_logGas + c_logTopicGas * n; + gas += memoryGas(0, -1); + if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) + gas += c_logDataGas * (*value); + else + gas = GasConsumption::infinite(); + break; + } case Instruction::CALL: case Instruction::CALLCODE: + gas = c_callGas; + if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) + gas += (*value); + else + gas = GasConsumption::infinite(); + if (_item.instruction() != Instruction::CALLCODE) + gas += c_callNewAccountGas; // We very rarely know whether the address exists. + if (!classes.knownZero(m_state->relativeStackElement(-2))) + gas += c_callValueTransferGas; + gas += memoryGas(-3, -4); + gas += memoryGas(-5, -6); + break; case Instruction::CREATE: + gas = c_createGas; + gas += memoryGas(-1, -2); + break; case Instruction::EXP: - // @todo logic can be improved - gas = GasConsumption::infinite(); + gas = c_expGas; + if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) + gas += c_expByteGas * (32 - (h256(*value).firstBitSet() / 8)); + else + gas = GasConsumption::infinite(); break; default: break; } - return gas; break; } default: + gas = GasConsumption::infinite(); break; } - return GasConsumption::infinite(); + m_state->feedItem(_item); + return gas; +} + +GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _position) +{ + u256 const* value = m_state->expressionClasses().knownConstant(_position); + if (!value) + return GasConsumption::infinite(); + return GasConsumption(_multiplier * ((*value + 31) / 32)); +} + +GasMeter::GasConsumption GasMeter::memoryGas(ExpressionClasses::Id _position) +{ + u256 const* value = m_state->expressionClasses().knownConstant(_position); + if (!value) + return GasConsumption::infinite(); + if (*value < m_largestMemoryAccess) + return GasConsumption(u256(0)); + u256 previous = m_largestMemoryAccess; + m_largestMemoryAccess = *value; + auto memGas = [](u256 const& pos) -> u256 + { + u256 size = (pos + 31) / 32; + return c_memoryGas * size + size * size / c_quadCoeffDiv; + }; + return memGas(*value) - memGas(previous); +} + +GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosSize) +{ + ExpressionClasses& classes = m_state->expressionClasses(); + if (classes.knownZero(m_state->relativeStackElement(_stackPosSize))) + return GasConsumption(0); + else + return memoryGas(classes.find(eth::Instruction::ADD, { + m_state->relativeStackElement(_stackPosOffset), + m_state->relativeStackElement(_stackPosSize) + })); } GasMeter::GasConsumption GasMeter::runGas(Instruction _instruction) diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 63dbc1380..ab6d5613b 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include namespace dev @@ -29,8 +30,13 @@ namespace dev namespace eth { +class KnownState; + /** * Class that helps computing the maximum gas consumption for instructions. + * Has to be initialized with a certain known state that will be automatically updated for + * each call to estimateMax. These calls have to supply strictly subsequent AssemblyItems. + * A new gas meter has to be constructed (with a new state) for control flow changes. */ class GasMeter { @@ -47,11 +53,28 @@ public: bool isInfinite; }; - /// Returns an upper bound on the gas consumed by the given instruction. + /// Constructs a new gas meter given the current state. + GasMeter(std::shared_ptr const& _state): m_state(_state) {} + + /// @returns an upper bound on the gas consumed by the given instruction and updates + /// the state. GasConsumption estimateMax(AssemblyItem const& _item); private: + /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. + GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value); + /// @returns the gas needed to access the given memory position. + /// @todo this assumes that memory was never accessed before and thus over-estimates gas usage. + GasConsumption memoryGas(ExpressionClasses::Id _position); + /// @returns the memory gas for accessing the memory at a specific offset for a number of bytes + /// given as values on the stack at the given relative positions. + GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize); + static GasConsumption runGas(Instruction _instruction); + + std::shared_ptr m_state; + /// Largest point where memory was accessed since the creation of this object. + u256 m_largestMemoryAccess; }; inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption) @@ -59,7 +82,7 @@ inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption con if (_consumption.isInfinite) return _str << "inf"; else - return _str << _consumption.value; + return _str << std::dec << _consumption.value; } diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp index 895778ed1..d62dbf17e 100644 --- a/libevmasm/KnownState.cpp +++ b/libevmasm/KnownState.cpp @@ -92,7 +92,11 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool else if (_item.type() != Operation) { assertThrow(_item.deposit() == 1, InvalidDeposit, ""); - setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem)); + if (_item.pushedValue()) + // only available after assembly stage, should not be used for optimisation + setStackElement(++m_stackHeight, m_expressionClasses->find(*_item.pushedValue())); + else + setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem)); } else { @@ -230,7 +234,12 @@ ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation return m_stackElements.at(_stackHeight); // Stack element not found (not assigned yet), create new unknown equivalence class. return m_stackElements[_stackHeight] = - m_expressionClasses->find(AssemblyItem(UndefinedItem, _stackHeight, _location)); + m_expressionClasses->find(AssemblyItem(UndefinedItem, _stackHeight, _location)); +} + +KnownState::Id KnownState::relativeStackElement(int _stackOffset, SourceLocation const& _location) +{ + return stackElement(m_stackHeight + _stackOffset, _location); } void KnownState::clearTagUnions() diff --git a/libevmasm/KnownState.h b/libevmasm/KnownState.h index 3505df74f..9d28ef21a 100644 --- a/libevmasm/KnownState.h +++ b/libevmasm/KnownState.h @@ -111,6 +111,8 @@ public: /// Retrieves the current equivalence class fo the given stack element (or generates a new /// one if it does not exist yet). Id stackElement(int _stackHeight, SourceLocation const& _location); + /// @returns the stackElement relative to the current stack height. + Id relativeStackElement(int _stackOffset, SourceLocation const& _location = SourceLocation()); /// @returns its set of tags if the given expression class is a known tag union; returns a set /// containing the tag if it is a PushTag expression and the empty set otherwise. @@ -123,6 +125,8 @@ public: std::map const& stackElements() const { return m_stackElements; } ExpressionClasses& expressionClasses() const { return *m_expressionClasses; } + std::map const& storageContent() const { return m_storageContent; } + private: /// Assigns a new equivalence class to the next sequence number of the given stack element. void setStackElement(int _stackHeight, Id _class); diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index fbda50791..f78472208 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -220,6 +220,26 @@ protected: virtual void endVisitNode(ASTNode const&) { } }; +/** + * Utility class that accepts std::functions and calls them for visitNode and endVisitNode. + */ +class SimpleASTVisitor: public ASTConstVisitor +{ +public: + SimpleASTVisitor( + std::function _onVisit, + std::function _onEndVisit + ): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {} + +protected: + virtual bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; } + virtual void endVisitNode(ASTNode const& _n) override { m_onEndVisit(_n); } + +private: + std::function m_onVisit; + std::function m_onEndVisit; +}; + /** * Utility class that visits the AST in depth-first order and calls a function on each node and each edge. * Child nodes are only visited if the node callback of the parent returns true. diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index bffa4158f..4f9764075 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -55,12 +55,29 @@ const map StandardSources = map{ }; CompilerStack::CompilerStack(bool _addStandardSources): - m_addStandardSources(_addStandardSources), m_parseSuccessful(false) + m_parseSuccessful(false) { - if (m_addStandardSources) + if (_addStandardSources) addSources(StandardSources, true); // add them as libraries } +void CompilerStack::reset(bool _keepSources, bool _addStandardSources) +{ + m_parseSuccessful = false; + if (_keepSources) + for (auto sourcePair: m_sources) + sourcePair.second.reset(); + else + { + m_sources.clear(); + if (_addStandardSources) + addSources(StandardSources, true); + } + m_globalContext.reset(); + m_sourceOrder.clear(); + m_contracts.clear(); +} + bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) { bool existed = m_sources.count(_name) != 0; @@ -269,23 +286,6 @@ tuple CompilerStack::positionFromSourceLocation(SourceLocati return make_tuple(++startLine, ++startColumn, ++endLine, ++endColumn); } -void CompilerStack::reset(bool _keepSources) -{ - m_parseSuccessful = false; - if (_keepSources) - for (auto sourcePair: m_sources) - sourcePair.second.reset(); - else - { - m_sources.clear(); - if (m_addStandardSources) - addSources(StandardSources, true); - } - m_globalContext.reset(); - m_sourceOrder.clear(); - m_contracts.clear(); -} - void CompilerStack::resolveImports() { // topological sorting (depth first search) of the import graph, cutting potential cycles diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 2ad791f22..0bc109a26 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -72,6 +72,9 @@ public: /// Creates a new compiler stack. Adds standard sources if @a _addStandardSources. explicit CompilerStack(bool _addStandardSources = true); + /// Resets the compiler to a state where the sources are not parsed or even removed. + void reset(bool _keepSources = false, bool _addStandardSources = true); + /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. /// @returns true if a source object by the name already existed and was replaced. void addSources(StringMap const& _nameContents, bool _isLibrary = false) { for (auto const& i: _nameContents) addSource(i.first, i.second, _isLibrary); } @@ -165,13 +168,11 @@ private: Contract(); }; - void reset(bool _keepSources = false); void resolveImports(); Contract const& getContract(std::string const& _contractName = "") const; Source const& getSource(std::string const& _sourceName = "") const; - bool m_addStandardSources; ///< If true, standard sources are added. bool m_parseSuccessful; std::map m_sources; std::shared_ptr m_globalContext; diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index 85026ac12..9a2acd5ba 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -107,17 +107,27 @@ std::unique_ptr InterfaceHandler::getABIInterface(ContractDefinitio unique_ptr InterfaceHandler::getABISolidityInterface(ContractDefinition const& _contractDef) { string ret = "contract " + _contractDef.getName() + "{"; + + auto populateParameters = [](vector const& _paramNames, vector const& _paramTypes) + { + string r = ""; + solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _paramNames.size(); ++i) + r += (r.size() ? "," : "(") + _paramTypes[i] + " " + _paramNames[i]; + return r.size() ? r + ")" : "()"; + }; + if (_contractDef.getConstructor()) + { + auto externalFunction = FunctionType(*_contractDef.getConstructor()).externalFunctionType(); + solAssert(!!externalFunction, ""); + ret += + "function " + + _contractDef.getName() + + populateParameters(externalFunction->getParameterNames(), externalFunction->getParameterTypeNames()) + + ";"; + } for (auto const& it: _contractDef.getInterfaceFunctions()) { - auto populateParameters = [](vector const& _paramNames, - vector const& _paramTypes) - { - string r = ""; - solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); - for (unsigned i = 0; i < _paramNames.size(); ++i) - r += (r.size() ? "," : "(") + _paramTypes[i] + " " + _paramNames[i]; - return r.size() ? r + ")" : "()"; - }; ret += "function " + it.second->getDeclaration().getName() + populateParameters(it.second->getParameterNames(), it.second->getParameterTypeNames()) + (it.second->isConstant() ? "constant " : ""); diff --git a/libsolidity/StructuralGasEstimator.cpp b/libsolidity/StructuralGasEstimator.cpp index ececd7116..9ce32ca54 100644 --- a/libsolidity/StructuralGasEstimator.cpp +++ b/libsolidity/StructuralGasEstimator.cpp @@ -23,6 +23,9 @@ #include "StructuralGasEstimator.h" #include #include +#include +#include +#include #include #include @@ -38,14 +41,23 @@ StructuralGasEstimator::ASTGasConsumptionSelfAccumulated StructuralGasEstimator: { solAssert(std::count(_ast.begin(), _ast.end(), nullptr) == 0, ""); map particularCosts; - GasMeter meter; - for (auto const& item: _items) - particularCosts[item.getLocation()] += meter.estimateMax(item); + ControlFlowGraph cfg(_items); + for (BasicBlock const& block: cfg.optimisedBlocks()) + { + assertThrow(!!block.startState, OptimizerException, ""); + GasMeter meter(block.startState->copy()); + auto const end = _items.begin() + block.end; + for (auto iter = _items.begin() + block.begin; iter != end; ++iter) + particularCosts[iter->getLocation()] += meter.estimateMax(*iter); + } + set finestNodes = finestNodesAtLocation(_ast); ASTGasConsumptionSelfAccumulated gasCosts; auto onNode = [&](ASTNode const& _node) { + if (!finestNodes.count(&_node)) + return true; gasCosts[&_node][0] = gasCosts[&_node][1] = particularCosts[_node.getLocation()]; return true; }; @@ -108,3 +120,24 @@ map StructuralGasEstimator::breakToSta // gasCosts should only contain non-overlapping locations return gasCosts; } + +set StructuralGasEstimator::finestNodesAtLocation( + vector const& _roots +) +{ + map locations; + set nodes; + SimpleASTVisitor visitor(function(), [&](ASTNode const& _n) + { + if (!locations.count(_n.getLocation())) + { + locations[_n.getLocation()] = &_n; + nodes.insert(&_n); + } + }); + + for (ASTNode const* root: _roots) + root->accept(visitor); + return nodes; +} + diff --git a/libsolidity/StructuralGasEstimator.h b/libsolidity/StructuralGasEstimator.h index df1ae509d..ddc7c186c 100644 --- a/libsolidity/StructuralGasEstimator.h +++ b/libsolidity/StructuralGasEstimator.h @@ -56,6 +56,10 @@ public: ASTGasConsumptionSelfAccumulated const& _gasCosts, std::vector const& _roots ); + +private: + /// @returns the set of AST nodes which are the finest nodes at their location. + std::set finestNodesAtLocation(std::vector const& _roots); }; } diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index aada83041..476d1ecf9 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -327,7 +327,8 @@ void ImportTest::checkExpectedState(State const& _stateExpect, State const& _sta void ImportTest::exportTest(bytes const& _output, State const& _statePost) { // export output - m_TestObject["out"] = toHex(_output, 2, HexPrefix::Add); + + m_TestObject["out"] = _output.size() > 4096 ? "#" + toString(_output.size()) : toHex(_output, 2, HexPrefix::Add); // export logs m_TestObject["logs"] = exportLog(_statePost.pending().size() ? _statePost.log(0) : LogEntries()); @@ -489,7 +490,11 @@ LogEntries importLog(json_spirit::mArray& _a) void checkOutput(bytes const& _output, json_spirit::mObject& _o) { int j = 0; - if (_o["out"].type() == json_spirit::array_type) + + if (_o["out"].get_str().find("#") == 0) + BOOST_CHECK((u256)_output.size() == toInt(_o["out"].get_str().substr(1))); + + else if (_o["out"].type() == json_spirit::array_type) for (auto const& d: _o["out"].get_array()) { BOOST_CHECK_MESSAGE(_output[j] == toInt(d), "Output byte [" << j << "] different!"); diff --git a/test/libethereum/StateTestsFiller/stMemoryStressTestFiller.json b/test/libethereum/StateTestsFiller/stMemoryStressTestFiller.json index 3893223eb..b5a531520 100644 --- a/test/libethereum/StateTestsFiller/stMemoryStressTestFiller.json +++ b/test/libethereum/StateTestsFiller/stMemoryStressTestFiller.json @@ -58,7 +58,7 @@ "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { "balance" : "1000000000000000000", "nonce" : "0", - "code" : "{ (RETURN 0 4294967297) } ", + "code" : "{ (RETURN 0 4294967295) } ", "storage": {} }, "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { @@ -77,7 +77,7 @@ "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", "data" : "" } - }, + }, "mload32bitBound_return2": { "env" : { @@ -98,7 +98,7 @@ "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { "balance" : "1000000000000000000", "nonce" : "0", - "code" : "{[ 0 ] 1 (RETURN 0 4294967296) } ", + "code" : "{[ 0 ] 1 (RETURN 0 4294967295) } ", "storage": {} }, "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { diff --git a/test/libethereum/state.cpp b/test/libethereum/state.cpp index fe0f10ca3..ed5cf14ac 100644 --- a/test/libethereum/state.cpp +++ b/test/libethereum/state.cpp @@ -176,12 +176,10 @@ BOOST_AUTO_TEST_CASE(stMemoryStressTest) dev::test::executeTests("stMemoryStressTest", "/StateTests",dev::test::getFolder(__FILE__) + "/StateTestsFiller", dev::test::doStateTests); } -#if ETH_SOLIDITY BOOST_AUTO_TEST_CASE(stSolidityTest) { dev::test::executeTests("stSolidityTest", "/StateTests",dev::test::getFolder(__FILE__) + "/StateTestsFiller", dev::test::doStateTests); } -#endif BOOST_AUTO_TEST_CASE(stMemoryTest) { diff --git a/test/libevm/vm.cpp b/test/libevm/vm.cpp index bda751af4..7b589d238 100644 --- a/test/libevm/vm.cpp +++ b/test/libevm/vm.cpp @@ -390,7 +390,7 @@ void doVMTests(json_spirit::mValue& v, bool _fillin) } o["callcreates"] = fev.exportCallCreates(); - o["out"] = toHex(output, 2, HexPrefix::Add); + o["out"] = output.size() > 4096 ? "#" + toString(output.size()) : toHex(output, 2, HexPrefix::Add); o["gas"] = toCompactHex(gas, HexPrefix::Add, 1); o["logs"] = exportLog(fev.sub.logs); } diff --git a/test/GasMeter.cpp b/test/libsolidity/GasMeter.cpp similarity index 66% rename from test/GasMeter.cpp rename to test/libsolidity/GasMeter.cpp index 0ffe41712..43eb3f956 100644 --- a/test/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -21,6 +21,8 @@ */ #include +#include +#include #include #include #include @@ -55,8 +57,21 @@ public: ); } + void testCreationTimeGas(string const& _sourceCode, string const& _contractName = "") + { + compileAndRun(_sourceCode); + auto state = make_shared(); + GasMeter meter(state); + GasMeter::GasConsumption gas; + for (AssemblyItem const& item: *m_compiler.getAssemblyItems(_contractName)) + gas += meter.estimateMax(item); + u256 bytecodeSize(m_compiler.getRuntimeBytecode(_contractName).size()); + gas += bytecodeSize * c_createDataGas; + BOOST_REQUIRE(!gas.isInfinite); + BOOST_CHECK(gas.value == m_gasUsed); + } + protected: - dev::solidity::CompilerStack m_compiler; map m_gasCosts; }; @@ -91,6 +106,49 @@ BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs) } } +BOOST_AUTO_TEST_CASE(simple_contract) +{ + // Tests a simple "deploy contract" code without constructor. The actual contract is not relevant. + char const* sourceCode = R"( + contract test { + bytes32 public shaValue; + function f(uint a) { + shaValue = sha3(a); + } + } + )"; + testCreationTimeGas(sourceCode); +} + +BOOST_AUTO_TEST_CASE(store_sha3) +{ + char const* sourceCode = R"( + contract test { + bytes32 public shaValue; + function test(uint a) { + shaValue = sha3(a); + } + } + )"; + testCreationTimeGas(sourceCode); +} + +BOOST_AUTO_TEST_CASE(updating_store) +{ + char const* sourceCode = R"( + contract test { + uint data; + uint data2; + function test() { + data = 1; + data = 2; + data2 = 0; + } + } + )"; + testCreationTimeGas(sourceCode); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 90ce20d26..503615a5a 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4023,6 +4023,92 @@ BOOST_AUTO_TEST_CASE(overwriting_inheritance) BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(6)); } +BOOST_AUTO_TEST_CASE(struct_assign_reference_to_struct) +{ + char const* sourceCode = R"( + contract test { + struct testStruct + { + uint m_value; + } + testStruct data1; + testStruct data2; + testStruct data3; + function test() + { + data1.m_value = 2; + } + function assign() returns (uint ret_local, uint ret_global, uint ret_global3, uint ret_global1) + { + testStruct x = data1; //x is a reference data1.m_value == 2 as well as x.m_value = 2 + data2 = data1; // should copy data. data2.m_value == 2 + + ret_local = x.m_value; // = 2 + ret_global = data2.m_value; // = 2 + + x.m_value = 3; + data3 = x; //should copy the data. data3.m_value == 3 + ret_global3 = data3.m_value; // = 3 + ret_global1 = data1.m_value; // = 3. Changed due to the assignment to x.m_value + } + } + )"; + compileAndRun(sourceCode, 0, "test"); + BOOST_CHECK(callContractFunction("assign()") == encodeArgs(2, 2, 3, 3)); +} + +BOOST_AUTO_TEST_CASE(struct_delete_member) +{ + char const* sourceCode = R"( + contract test { + struct testStruct + { + uint m_value; + } + testStruct data1; + function test() + { + data1.m_value = 2; + } + function deleteMember() returns (uint ret_value) + { + testStruct x = data1; //should not copy the data. data1.m_value == 2 but x.m_value = 0 + x.m_value = 4; + delete x.m_value; + ret_value = data1.m_value; + } + } + )"; + compileAndRun(sourceCode, 0, "test"); + auto res = callContractFunction("deleteMember()"); + BOOST_CHECK(callContractFunction("deleteMember()") == encodeArgs(0)); +} + +BOOST_AUTO_TEST_CASE(struct_delete_struct_in_mapping) +{ + char const* sourceCode = R"( + contract test { + struct testStruct + { + uint m_value; + } + mapping (uint => testStruct) campaigns; + + function test() + { + campaigns[0].m_value = 2; + } + function deleteIt() returns (uint) + { + delete campaigns[0]; + return campaigns[0].m_value; + } + } + )"; + compileAndRun(sourceCode, 0, "test"); + auto res = callContractFunction("deleteIt()"); + BOOST_CHECK(callContractFunction("deleteIt()") == encodeArgs(0)); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/solidityExecutionFramework.h b/test/libsolidity/solidityExecutionFramework.h index f76465f23..fa25fb12c 100644 --- a/test/libsolidity/solidityExecutionFramework.h +++ b/test/libsolidity/solidityExecutionFramework.h @@ -44,11 +44,11 @@ public: bytes const& compileAndRun(std::string const& _sourceCode, u256 const& _value = 0, std::string const& _contractName = "") { - dev::solidity::CompilerStack compiler(m_addStandardSources); - compiler.addSource("", _sourceCode); - ETH_TEST_REQUIRE_NO_THROW(compiler.compile(m_optimize), "Compiling contract failed"); + m_compiler.reset(false, m_addStandardSources); + m_compiler.addSource("", _sourceCode); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize), "Compiling contract failed"); - bytes code = compiler.getBytecode(_contractName); + bytes code = m_compiler.getBytecode(_contractName); sendMessage(code, true, _value); BOOST_REQUIRE(!m_output.empty()); return m_output; @@ -160,12 +160,14 @@ protected: BOOST_REQUIRE(executive.go()); m_state.noteSending(m_sender); executive.finalize(); + m_gasUsed = executive.gasUsed(); m_output = executive.out().toVector(); m_logs = executive.logs(); } bool m_optimize = false; bool m_addStandardSources = false; + dev::solidity::CompilerStack m_compiler; Address m_sender; Address m_contractAddress; eth::State m_state; @@ -173,6 +175,7 @@ protected: u256 const m_gas = 100000000; bytes m_output; eth::LogEntries m_logs; + u256 m_gasUsed; }; }