From 02717e8b0c395408af36543b13597bb828de6732 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 22 May 2015 09:33:57 +0200 Subject: [PATCH 1/8] Path gas meter. --- libevmasm/GasMeter.cpp | 5 +- libevmasm/GasMeter.h | 12 ++- libevmasm/PathGasMeter.cpp | 128 ++++++++++++++++++++++++++++++ libevmasm/PathGasMeter.h | 66 +++++++++++++++ libevmasm/SemanticInformation.cpp | 2 +- test/libsolidity/GasMeter.cpp | 70 ++++++++++++++-- 6 files changed, 271 insertions(+), 12 deletions(-) create mode 100644 libevmasm/PathGasMeter.cpp create mode 100644 libevmasm/PathGasMeter.h diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index a8dc4dd58..3749e635d 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -29,12 +29,13 @@ using namespace dev::eth; GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption const& _other) { - isInfinite = isInfinite || _other.isInfinite; + if (_other.isInfinite && !isInfinite) + *this = infinite(); if (isInfinite) return *this; bigint v = bigint(value) + _other.value; if (v > std::numeric_limits::max()) - isInfinite = true; + *this = infinite(); else value = u256(v); return *this; diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index ab6d5613b..95593b565 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include @@ -46,20 +47,25 @@ public: GasConsumption(u256 _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {} static GasConsumption infinite() { return GasConsumption(0, true); } - GasConsumption& operator+=(GasConsumption const& _otherS); - std::ostream& operator<<(std::ostream& _str) const; + GasConsumption& operator+=(GasConsumption const& _other); + bool operator<(GasConsumption const& _other) const { return this->tuple() < _other.tuple(); } + + std::tuple tuple() const { return std::tie(isInfinite, value); } u256 value; bool isInfinite; }; /// Constructs a new gas meter given the current state. - GasMeter(std::shared_ptr const& _state): m_state(_state) {} + explicit GasMeter(std::shared_ptr const& _state, u256 const& _largestMemoryAccess = 0): + m_state(_state), m_largestMemoryAccess(_largestMemoryAccess) {} /// @returns an upper bound on the gas consumed by the given instruction and updates /// the state. GasConsumption estimateMax(AssemblyItem const& _item); + u256 const& largestMemoryAccess() const { return m_largestMemoryAccess; } + private: /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value); diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp new file mode 100644 index 000000000..8f7314f89 --- /dev/null +++ b/libevmasm/PathGasMeter.cpp @@ -0,0 +1,128 @@ +/* + 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 . +*/ +/** @file PathGasMeter.cpp + * @author Christian + * @date 2015 + */ + +#include "PathGasMeter.h" +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + +PathGasMeter::PathGasMeter(AssemblyItems const& _items): + m_items(_items) +{ + for (size_t i = 0; i < m_items.size(); ++i) + if (m_items[i].type() == Tag) + m_tagPositions[m_items[i].data()] = i; +} + +GasMeter::GasConsumption PathGasMeter::estimateMax( + size_t _startIndex, + shared_ptr const& _state +) +{ + auto path = unique_ptr(new GasPath()); + path->index = _startIndex; + path->state = _state->copy(); + m_queue.push_back(move(path)); + + GasMeter::GasConsumption gas; + while (!m_queue.empty() && !gas.isInfinite) + gas = max(gas, handleQueueItem()); + return gas; +} + +GasMeter::GasConsumption PathGasMeter::handleQueueItem() +{ + assertThrow(!m_queue.empty(), OptimizerException, ""); + + unique_ptr path = move(m_queue.back()); + m_queue.pop_back(); + + shared_ptr state = path->state; + GasMeter meter(state, path->largestMemoryAccess); + ExpressionClasses& classes = state->expressionClasses(); + GasMeter::GasConsumption gas = path->gas; + size_t index = path->index; + + if (index >= m_items.size() || (index > 0 && m_items.at(index).type() != Tag)) + // Invalid jump usually provokes an out-of-gas exception, but we want to give an upper + // bound on the gas that is needed without changing the behaviour, so it is fine to + // return the current gas value. + return gas; + + set jumpTags; + for (; index < m_items.size() && !gas.isInfinite; ++index) + { + bool branchStops = false; + jumpTags.clear(); + AssemblyItem const& item = m_items.at(index); + if (item.type() == Tag || item == AssemblyItem(eth::Instruction::JUMPDEST)) + { + // Do not allow any backwards jump. This is quite restrictive but should work for + // the simplest things. + if (path->visitedJumpdests.count(index)) + return GasMeter::GasConsumption::infinite(); + path->visitedJumpdests.insert(index); + } + else if (item == AssemblyItem(eth::Instruction::JUMP)) + { + branchStops = true; + jumpTags = state->tagsInExpression(state->relativeStackElement(0)); + if (jumpTags.empty()) // unknown jump destination + return GasMeter::GasConsumption::infinite(); + } + else if (item == AssemblyItem(eth::Instruction::JUMPI)) + { + ExpressionClasses::Id condition = state->relativeStackElement(-1); + if (classes.knownNonZero(condition) || !classes.knownZero(condition)) + { + jumpTags = state->tagsInExpression(state->relativeStackElement(0)); + if (jumpTags.empty()) // unknown jump destination + return GasMeter::GasConsumption::infinite(); + } + branchStops = classes.knownNonZero(condition); + } + else if (SemanticInformation::altersControlFlow(item)) + branchStops = true; + + gas += meter.estimateMax(item); + + for (u256 const& tag: jumpTags) + { + auto newPath = unique_ptr(new GasPath()); + newPath->index = m_items.size(); + if (m_tagPositions.count(tag)) + newPath->index = m_tagPositions.at(tag); + newPath->gas = gas; + newPath->largestMemoryAccess = meter.largestMemoryAccess(); + newPath->state = state->copy(); + newPath->visitedJumpdests = path->visitedJumpdests; + m_queue.push_back(move(newPath)); + } + + if (branchStops) + break; + } + + return gas; +} diff --git a/libevmasm/PathGasMeter.h b/libevmasm/PathGasMeter.h new file mode 100644 index 000000000..1ada460aa --- /dev/null +++ b/libevmasm/PathGasMeter.h @@ -0,0 +1,66 @@ +/* + 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 . +*/ +/** @file PathGasMeter.cpp + * @author Christian + * @date 2015 + */ + +#pragma once + +#include +#include +#include +#include + +namespace dev +{ +namespace eth +{ + +class KnownState; + +struct GasPath +{ + size_t index = 0; + std::shared_ptr state; + u256 largestMemoryAccess; + GasMeter::GasConsumption gas; + std::set visitedJumpdests; +}; + +/** + * Computes an upper bound on the gas usage of a computation starting at a certain position in + * a list of AssemblyItems in a given state until the computation stops. + * Can be used to estimate the gas usage of functions on any given input. + */ +class PathGasMeter +{ +public: + PathGasMeter(AssemblyItems const& _items); + + GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr const& _state); + +private: + GasMeter::GasConsumption handleQueueItem(); + + std::vector> m_queue; + std::map m_tagPositions; + AssemblyItems const& m_items; +}; + +} +} diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 056162b5f..91f93e7ef 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -111,7 +111,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) switch (_item.instruction()) { // note that CALL, CALLCODE and CREATE do not really alter the control flow, because we - // continue on the next instruction (unless an exception happens which can always happen) + // continue on the next instruction case Instruction::JUMP: case Instruction::JUMPI: case Instruction::RETURN: diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 43eb3f956..2508399ff 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -57,20 +58,38 @@ public: ); } - void testCreationTimeGas(string const& _sourceCode, string const& _contractName = "") + void testCreationTimeGas(string const& _sourceCode) { 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()); + PathGasMeter meter(*m_compiler.getAssemblyItems()); + GasMeter::GasConsumption gas = meter.estimateMax(0, state); + u256 bytecodeSize(m_compiler.getRuntimeBytecode().size()); gas += bytecodeSize * c_createDataGas; BOOST_REQUIRE(!gas.isInfinite); BOOST_CHECK(gas.value == m_gasUsed); } + void testRunTimeGas(std::string const& _sig, vector _argumentVariants) + { + u256 gasUsed = 0; + FixedHash<4> hash(dev::sha3(_sig)); + for (bytes const& arguments: _argumentVariants) + { + sendMessage(hash.asBytes() + arguments, false, 0); + gasUsed = max(gasUsed, m_gasUsed); + } + + auto state = make_shared(); + //TODO modify state to include function hash in calldata + PathGasMeter meter(*m_compiler.getRuntimeAssemblyItems()); + GasMeter::GasConsumption gas = meter.estimateMax(0, state); + cout << "VM: " << gasUsed << endl; + cout << "est: " << gas << endl; + BOOST_REQUIRE(!gas.isInfinite); + BOOST_CHECK(gas.value == m_gasUsed); + } + protected: map m_gasCosts; }; @@ -149,6 +168,45 @@ BOOST_AUTO_TEST_CASE(updating_store) testCreationTimeGas(sourceCode); } +BOOST_AUTO_TEST_CASE(branches) +{ + char const* sourceCode = R"( + contract test { + uint data; + uint data2; + function f(uint x) { + if (x > 7) + data2 = 1; + else + data = 1; + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("f(uint256)", vector{encodeArgs(2), encodeArgs(8)}); +} + +BOOST_AUTO_TEST_CASE(function_calls) +{ + char const* sourceCode = R"( + contract test { + uint data; + uint data2; + function f(uint x) { + if (x > 7) + data2 = g(x**8) + 1; + else + data = 1; + } + function g(uint x) internal returns (uint) { + return data2; + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("f(uint256)", vector{encodeArgs(2), encodeArgs(8)}); +} + BOOST_AUTO_TEST_SUITE_END() } From 9e2e73da3a5c2fc1dc7f720636f64f5b000bdc0b Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 22 May 2015 10:48:54 +0200 Subject: [PATCH 2/8] Functional gas estimator. --- libevmasm/ExpressionClasses.cpp | 27 ++++++++++-- libevmasm/ExpressionClasses.h | 5 +++ libsolidity/ASTPrinter.cpp | 2 +- libsolidity/ASTPrinter.h | 6 +-- ...turalGasEstimator.cpp => GasEstimator.cpp} | 36 ++++++++++++---- ...tructuralGasEstimator.h => GasEstimator.h} | 20 ++++++--- mix/CodeModel.cpp | 5 +-- solc/CommandLineInterface.cpp | 7 ++-- test/libsolidity/GasMeter.cpp | 41 ++++++++++++++----- 9 files changed, 112 insertions(+), 37 deletions(-) rename libsolidity/{StructuralGasEstimator.cpp => GasEstimator.cpp} (79%) rename libsolidity/{StructuralGasEstimator.h => GasEstimator.h} (72%) diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index 81adc0dbb..81ba11541 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -57,11 +57,11 @@ ExpressionClasses::Id ExpressionClasses::find( exp.arguments = _arguments; exp.sequenceNumber = _sequenceNumber; + if (SemanticInformation::isCommutativeOperation(_item)) + sort(exp.arguments.begin(), exp.arguments.end()); + if (SemanticInformation::isDeterministic(_item)) { - if (SemanticInformation::isCommutativeOperation(_item)) - sort(exp.arguments.begin(), exp.arguments.end()); - auto it = m_expressions.find(exp); if (it != m_expressions.end()) return it->id; @@ -82,6 +82,27 @@ ExpressionClasses::Id ExpressionClasses::find( return exp.id; } +void ExpressionClasses::forceEqual( + ExpressionClasses::Id _id, + AssemblyItem const& _item, + ExpressionClasses::Ids const& _arguments, + bool _copyItem +) +{ + Expression exp; + exp.id = _id; + exp.item = &_item; + exp.arguments = _arguments; + + if (SemanticInformation::isCommutativeOperation(_item)) + sort(exp.arguments.begin(), exp.arguments.end()); + + if (_copyItem) + exp.item = storeItem(_item); + + m_expressions.insert(exp); +} + ExpressionClasses::Id ExpressionClasses::newClass(SourceLocation const& _location) { Expression exp; diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index dd94092e8..4bfd7d24a 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -74,6 +74,11 @@ public: /// @returns the number of classes. Id size() const { return m_representatives.size(); } + /// Forces the given @a _item with @a _arguments to the class @a _id. This can be used to + /// add prior knowledge e.g. about CALLDATA, but has to be used with caution. Will not work as + /// expected if @a _item applied to @a _arguments already exists. + void forceEqual(Id _id, AssemblyItem const& _item, Ids const& _arguments, bool _copyItem = true); + /// @returns the id of a new class which is different to all other classes. Id newClass(SourceLocation const& _location); diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index 0a170f8e1..d29ace178 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -33,7 +33,7 @@ namespace solidity ASTPrinter::ASTPrinter( ASTNode const& _ast, string const& _source, - StructuralGasEstimator::ASTGasConsumption const& _gasCosts + GasEstimator::ASTGasConsumption const& _gasCosts ): m_indentation(0), m_source(_source), m_ast(&_ast), m_gasCosts(_gasCosts) { } diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index 25678c176..cdf651f3d 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -24,7 +24,7 @@ #include #include -#include +#include namespace dev { @@ -42,7 +42,7 @@ public: ASTPrinter( ASTNode const& _ast, std::string const& _source = std::string(), - StructuralGasEstimator::ASTGasConsumption const& _gasCosts = StructuralGasEstimator::ASTGasConsumption() + GasEstimator::ASTGasConsumption const& _gasCosts = GasEstimator::ASTGasConsumption() ); /// Output the string representation of the AST to _stream. void print(std::ostream& _stream); @@ -133,7 +133,7 @@ private: int m_indentation; std::string m_source; ASTNode const* m_ast; - StructuralGasEstimator::ASTGasConsumption m_gasCosts; + GasEstimator::ASTGasConsumption m_gasCosts; std::ostream* m_ostream; }; diff --git a/libsolidity/StructuralGasEstimator.cpp b/libsolidity/GasEstimator.cpp similarity index 79% rename from libsolidity/StructuralGasEstimator.cpp rename to libsolidity/GasEstimator.cpp index 9ce32ca54..18e3a5ca3 100644 --- a/libsolidity/StructuralGasEstimator.cpp +++ b/libsolidity/GasEstimator.cpp @@ -20,12 +20,14 @@ * Gas consumption estimator working alongside the AST. */ -#include "StructuralGasEstimator.h" +#include "GasEstimator.h" #include #include #include +#include #include #include +#include #include #include @@ -34,13 +36,13 @@ using namespace dev; using namespace dev::eth; using namespace dev::solidity; -StructuralGasEstimator::ASTGasConsumptionSelfAccumulated StructuralGasEstimator::performEstimation( +GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimation( AssemblyItems const& _items, vector const& _ast ) { solAssert(std::count(_ast.begin(), _ast.end(), nullptr) == 0, ""); - map particularCosts; + map particularCosts; ControlFlowGraph cfg(_items); for (BasicBlock const& block: cfg.optimisedBlocks()) @@ -72,7 +74,7 @@ StructuralGasEstimator::ASTGasConsumptionSelfAccumulated StructuralGasEstimator: return gasCosts; } -map StructuralGasEstimator::breakToStatementLevel( +map GasEstimator::breakToStatementLevel( ASTGasConsumptionSelfAccumulated const& _gasCosts, vector const& _roots ) @@ -99,7 +101,7 @@ map StructuralGasEstimator::breakToSta // we use the location of a node if // - its statement depth is 0 or // - its statement depth is undefined but the parent's statement depth is at least 1 - map gasCosts; + map gasCosts; auto onNodeSecondPass = [&](ASTNode const& _node) { return statementDepth.count(&_node); @@ -121,7 +123,28 @@ map StructuralGasEstimator::breakToSta return gasCosts; } -set StructuralGasEstimator::finestNodesAtLocation( +GasEstimator::GasConsumption GasEstimator::functionalEstimation( + AssemblyItems const& _items, + string const& _signature +) +{ + auto state = make_shared(); + + ExpressionClasses& classes = state->expressionClasses(); + using Id = ExpressionClasses::Id; + using Ids = vector; + Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::sha3(_signature))))); + Id calldata = classes.find(eth::Instruction::CALLDATALOAD, Ids{classes.find(u256(0))}); + classes.forceEqual(hashValue, eth::Instruction::DIV, Ids{ + calldata, + classes.find(u256(1) << (8 * 28)) + }); + + PathGasMeter meter(_items); + return meter.estimateMax(0, state); +} + +set GasEstimator::finestNodesAtLocation( vector const& _roots ) { @@ -140,4 +163,3 @@ set StructuralGasEstimator::finestNodesAtLocation( root->accept(visitor); return nodes; } - diff --git a/libsolidity/StructuralGasEstimator.h b/libsolidity/GasEstimator.h similarity index 72% rename from libsolidity/StructuralGasEstimator.h rename to libsolidity/GasEstimator.h index ddc7c186c..32e95fac4 100644 --- a/libsolidity/StructuralGasEstimator.h +++ b/libsolidity/GasEstimator.h @@ -34,17 +34,18 @@ namespace dev namespace solidity { -class StructuralGasEstimator +struct GasEstimator { public: - using ASTGasConsumption = std::map; + using GasConsumption = eth::GasMeter::GasConsumption; + using ASTGasConsumption = std::map; using ASTGasConsumptionSelfAccumulated = - std::map>; + std::map>; /// Estimates the gas consumption for every assembly item in the given assembly and stores /// it by source location. /// @returns a mapping from each AST node to a pair of its particular and syntactically accumulated gas costs. - ASTGasConsumptionSelfAccumulated performEstimation( + static ASTGasConsumptionSelfAccumulated structuralEstimation( eth::AssemblyItems const& _items, std::vector const& _ast ); @@ -52,14 +53,21 @@ public: /// the following source locations are part of the mapping: /// 1. source locations of statements that do not contain other statements /// 2. maximal source locations that do not overlap locations coming from the first rule - ASTGasConsumption breakToStatementLevel( + static ASTGasConsumption breakToStatementLevel( ASTGasConsumptionSelfAccumulated const& _gasCosts, std::vector const& _roots ); + /// @returns the estimated gas consumption by the (public or external) function with the + /// given signature. If no signature is given, estimates the maximum gas usage. + static GasConsumption functionalEstimation( + eth::AssemblyItems const& _items, + std::string const& _signature = "" + ); + private: /// @returns the set of AST nodes which are the finest nodes at their location. - std::set finestNodesAtLocation(std::vector const& _roots); + static std::set finestNodesAtLocation(std::vector const& _roots); }; } diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index a73dc920e..8d22227ce 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include #include #include @@ -371,8 +371,7 @@ void CodeModel::gasEstimation(solidity::CompilerStack const& _cs) continue; dev::solidity::SourceUnit const& sourceUnit = _cs.getAST(*contractDefinition.getLocation().sourceName); AssemblyItems const* items = _cs.getRuntimeAssemblyItems(n); - StructuralGasEstimator estimator; - std::map gasCosts = estimator.breakToStatementLevel(estimator.performEstimation(*items, std::vector({&sourceUnit})), {&sourceUnit}); + std::map gasCosts = GasEstimator::breakToStatementLevel(GasEstimator::structuralEstimation(*items, std::vector({&sourceUnit})), {&sourceUnit}); for (auto gasItem = gasCosts.begin(); gasItem != gasCosts.end(); ++gasItem) { SourceLocation const& location = gasItem->first->getLocation(); diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 944c8f68a..b81fcad1c 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -42,7 +42,7 @@ #include #include #include -#include +#include using namespace std; namespace po = boost::program_options; @@ -465,14 +465,13 @@ void CommandLineInterface::handleAst(string const& _argStr) // do we need AST output? if (m_args.count(_argStr)) { - StructuralGasEstimator gasEstimator; vector asts; for (auto const& sourceCode: m_sourceCodes) asts.push_back(&m_compiler->getAST(sourceCode.first)); map gasCosts; if (m_compiler->getRuntimeAssemblyItems()) - gasCosts = gasEstimator.breakToStatementLevel( - gasEstimator.performEstimation(*m_compiler->getRuntimeAssemblyItems(), asts), + gasCosts = GasEstimator::breakToStatementLevel( + GasEstimator::structuralEstimation(*m_compiler->getRuntimeAssemblyItems(), asts), asts ); diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 2508399ff..c09849c00 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include using namespace std; @@ -48,12 +48,11 @@ public: m_compiler.setSource(_sourceCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); - StructuralGasEstimator estimator; AssemblyItems const* items = m_compiler.getRuntimeAssemblyItems(""); ASTNode const& sourceUnit = m_compiler.getAST(); BOOST_REQUIRE(items != nullptr); - m_gasCosts = estimator.breakToStatementLevel( - estimator.performEstimation(*items, vector({&sourceUnit})), + m_gasCosts = GasEstimator::breakToStatementLevel( + GasEstimator::structuralEstimation(*items, vector({&sourceUnit})), {&sourceUnit} ); } @@ -70,6 +69,8 @@ public: BOOST_CHECK(gas.value == m_gasUsed); } + /// Compares the gas computed by PathGasMeter for the given signature (but unknown arguments) + /// against the actual gas usage computed by the VM on the given set of argument variants. void testRunTimeGas(std::string const& _sig, vector _argumentVariants) { u256 gasUsed = 0; @@ -80,12 +81,10 @@ public: gasUsed = max(gasUsed, m_gasUsed); } - auto state = make_shared(); - //TODO modify state to include function hash in calldata - PathGasMeter meter(*m_compiler.getRuntimeAssemblyItems()); - GasMeter::GasConsumption gas = meter.estimateMax(0, state); - cout << "VM: " << gasUsed << endl; - cout << "est: " << gas << endl; + GasMeter::GasConsumption gas = GasEstimator::functionalEstimation( + *m_compiler.getRuntimeAssemblyItems(), + _sig + ); BOOST_REQUIRE(!gas.isInfinite); BOOST_CHECK(gas.value == m_gasUsed); } @@ -207,6 +206,28 @@ BOOST_AUTO_TEST_CASE(function_calls) testRunTimeGas("f(uint256)", vector{encodeArgs(2), encodeArgs(8)}); } +BOOST_AUTO_TEST_CASE(multiple_external_functions) +{ + char const* sourceCode = R"( + contract test { + uint data; + uint data2; + function f(uint x) { + if (x > 7) + data2 = g(x**8) + 1; + else + data = 1; + } + function g(uint x) returns (uint) { + return data2; + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("f(uint256)", vector{encodeArgs(2), encodeArgs(8)}); + testRunTimeGas("g(uint256)", vector{encodeArgs(2)}); +} + BOOST_AUTO_TEST_SUITE_END() } From 1ccb9312fdb960d857f5a9ddffbff31b1375eab0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 22 May 2015 16:11:57 +0200 Subject: [PATCH 3/8] Tighter estimation for EXP. --- libevmasm/GasMeter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 3749e635d..650bd6e28 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -148,7 +148,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item) if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) gas += c_expByteGas * (32 - (h256(*value).firstBitSet() / 8)); else - gas = GasConsumption::infinite(); + gas += c_expByteGas * 32; break; default: break; From a09bb999cba3428e7ef7aa33301f4a55eef74fe4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 22 May 2015 14:19:58 +0200 Subject: [PATCH 4/8] Commandline interface for gas estimation. --- libevmasm/GasMeter.h | 2 +- solc/CommandLineInterface.cpp | 32 ++++++++++++++++++++++++++++++++ solc/CommandLineInterface.h | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 95593b565..6949c193e 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -86,7 +86,7 @@ private: inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption) { if (_consumption.isInfinite) - return _str << "inf"; + return _str << "[???]"; else return _str << std::dec << _consumption.value; } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index b81fcad1c..c86938f81 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ namespace solidity static string const g_argAbiStr = "json-abi"; static string const g_argSolAbiStr = "sol-abi"; static string const g_argSignatureHashes = "hashes"; +static string const g_argGas = "gas"; static string const g_argAsmStr = "asm"; static string const g_argAsmJsonStr = "asm-json"; static string const g_argAstStr = "ast"; @@ -94,6 +96,7 @@ static bool needsHumanTargetedStdout(po::variables_map const& _args) { return + _args.count(g_argGas) || humanTargetedStdout(_args, g_argAbiStr) || humanTargetedStdout(_args, g_argSolAbiStr) || humanTargetedStdout(_args, g_argSignatureHashes) || @@ -245,6 +248,30 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co } } +void CommandLineInterface::handleGasEstimation(string const& _contract) +{ + using Gas = GasEstimator::GasConsumption; + if (!m_compiler->getAssemblyItems(_contract) && !m_compiler->getRuntimeAssemblyItems(_contract)) + return; + cout << "Gas estimation:" << endl; + if (eth::AssemblyItems const* items = m_compiler->getAssemblyItems(_contract)) + { + Gas gas = GasEstimator::functionalEstimation(*items); + u256 bytecodeSize(m_compiler->getRuntimeBytecode(_contract).size()); + cout << "[construction]:\t"; + cout << gas << " + " << (bytecodeSize * eth::c_createDataGas) << " = "; + gas += bytecodeSize * eth::c_createDataGas; + cout << gas << endl; + } + if (eth::AssemblyItems const* items = m_compiler->getRuntimeAssemblyItems(_contract)) + for (auto it: m_compiler->getContractDefinition(_contract).getInterfaceFunctions()) + { + string sig = it.second->externalSignature(); + GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); + cout << sig << ":\t" << gas << endl; + } +} + bool CommandLineInterface::parseArguments(int argc, char** argv) { // Declare the supported options. @@ -278,6 +305,8 @@ bool CommandLineInterface::parseArguments(int argc, char** argv) "Request to output the contract's Solidity ABI interface.") (g_argSignatureHashes.c_str(), po::value()->value_name("stdout|file|both"), "Request to output the contract's functions' signature hashes.") + (g_argGas.c_str(), + "Request to output an estimate for each function's maximal gas usage.") (g_argNatspecUserStr.c_str(), po::value()->value_name("stdout|file|both"), "Request to output the contract's Natspec user documentation.") (g_argNatspecDevStr.c_str(), po::value()->value_name("stdout|file|both"), @@ -553,6 +582,9 @@ void CommandLineInterface::actOnInput() } } + if (m_args.count(g_argGas)) + handleGasEstimation(contract); + handleBytecode(contract); handleSignatureHashes(contract); handleMeta(DocumentationType::ABIInterface, contract); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 7d3a067cd..46b9b1e22 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -61,6 +61,7 @@ private: void handleSignatureHashes(std::string const& _contract); void handleMeta(DocumentationType _type, std::string const& _contract); + void handleGasEstimation(std::string const& _contract); /// Compiler arguments variable map boost::program_options::variables_map m_args; From 6281c11534c97e719ff228ee810d9c710b79a882 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 25 May 2015 11:49:38 +0200 Subject: [PATCH 5/8] Sort keywords and add some reserved keywords. --- libsolidity/Token.h | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 1435dcc57..8a373da34 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -142,34 +142,34 @@ namespace solidity K(Delete, "delete", 0) \ \ /* Keywords */ \ + K(Anonymous, "anonymous", 0) \ K(Break, "break", 0) \ K(Const, "constant", 0) \ - K(Anonymous, "anonymous", 0) \ K(Continue, "continue", 0) \ K(Contract, "contract", 0) \ K(Default, "default", 0) \ K(Do, "do", 0) \ K(Else, "else", 0) \ + K(Enum, "enum", 0) \ K(Event, "event", 0) \ K(External, "external", 0) \ - K(Is, "is", 0) \ - K(Indexed, "indexed", 0) \ K(For, "for", 0) \ K(Function, "function", 0) \ K(If, "if", 0) \ + K(Indexed, "indexed", 0) \ + K(Internal, "internal", 0) \ K(Import, "import", 0) \ + K(Is, "is", 0) \ K(Mapping, "mapping", 0) \ K(Modifier, "modifier", 0) \ K(New, "new", 0) \ K(Public, "public", 0) \ K(Private, "private", 0) \ - K(Internal, "internal", 0) \ K(Return, "return", 0) \ K(Returns, "returns", 0) \ K(Struct, "struct", 0) \ K(Var, "var", 0) \ K(While, "while", 0) \ - K(Enum, "enum", 0) \ \ /* Ether subdenominations */ \ K(SubWei, "wei", 0) \ @@ -304,15 +304,21 @@ namespace solidity T(Identifier, NULL, 0) \ \ /* Keywords reserved for future. use*/ \ - T(String, "string", 0) \ + K(As, "as", 0) \ K(Case, "case", 0) \ + K(Catch, "catch", 0) \ + K(Final, "final", 0) \ + K(Let, "let", 0) \ + K(Match, "match", 0) \ + K(Of, "of", 0) \ + K(Relocatable, "relocatable", 0) \ + T(String, "string", 0) \ K(Switch, "switch", 0) \ K(Throw, "throw", 0) \ K(Try, "try", 0) \ - K(Catch, "catch", 0) \ - K(Using, "using", 0) \ K(Type, "type", 0) \ K(TypeOf, "typeof", 0) \ + K(Using, "using", 0) \ /* Illegal token - not able to scan. */ \ T(Illegal, "ILLEGAL", 0) \ \ From 1a3d6904d70eaf40a97ac50cfe0db6cc5acc944f Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 26 May 2015 11:27:59 +0200 Subject: [PATCH 6/8] Gas estimation for internal functions. --- libsolidity/Compiler.cpp | 5 ++++ libsolidity/Compiler.h | 4 +++ libsolidity/CompilerContext.cpp | 6 +++++ libsolidity/CompilerContext.h | 4 +++ libsolidity/CompilerStack.cpp | 18 ++++++++++++++ libsolidity/CompilerStack.h | 8 ++++++ libsolidity/GasEstimator.cpp | 44 ++++++++++++++++++++++++++------- libsolidity/GasEstimator.h | 9 +++++++ solc/CommandLineInterface.cpp | 28 ++++++++++++++++++--- 9 files changed, 113 insertions(+), 13 deletions(-) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 66c503172..5e24aaaa2 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -71,6 +71,11 @@ void Compiler::compileContract(ContractDefinition const& _contract, packIntoContractCreator(_contract, m_runtimeContext); } +eth::AssemblyItem Compiler::getFunctionEntryLabel(FunctionDefinition const& _function) const +{ + return m_runtimeContext.getFunctionEntryLabelIfExists(_function); +} + void Compiler::initializeContext(ContractDefinition const& _contract, map const& _contracts) { diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 106038d1c..13b8639dd 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -52,6 +52,10 @@ public: /// @returns Assembly items of the runtime compiler context eth::AssemblyItems const& getRuntimeAssemblyItems() const { return m_runtimeContext.getAssembly().getItems(); } + /// @returns the entry label of the given function. Might return an AssemblyItem of type + /// UndefinedItem if it does not exist yet. + eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; + private: /// Registers the non-function objects inside the contract with the context. void initializeContext(ContractDefinition const& _contract, diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index f373fdfb0..2edff82e1 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -99,6 +99,12 @@ eth::AssemblyItem CompilerContext::getFunctionEntryLabel(Declaration const& _dec return res->second.tag(); } +eth::AssemblyItem CompilerContext::getFunctionEntryLabelIfExists(Declaration const& _declaration) const +{ + auto res = m_functionEntryLabels.find(&_declaration); + return res == m_functionEntryLabels.end() ? eth::AssemblyItem(eth::UndefinedItem) : res->second.tag(); +} + eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function) { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index 933912455..7bc29de1a 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -59,7 +59,11 @@ public: bool isLocalVariable(Declaration const* _declaration) const; bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } + /// @returns the entry label of the given function and creates it if it does not exist yet. eth::AssemblyItem getFunctionEntryLabel(Declaration const& _declaration); + /// @returns the entry label of the given function. Might return an AssemblyItem of type + /// UndefinedItem if it does not exist yet. + eth::AssemblyItem getFunctionEntryLabelIfExists(Declaration const& _declaration) const; void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function); diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index 4f9764075..ebb988768 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -268,6 +268,24 @@ ContractDefinition const& CompilerStack::getContractDefinition(string const& _co return *getContract(_contractName).contract; } +size_t CompilerStack::getFunctionEntryPoint( + std::string const& _contractName, + FunctionDefinition const& _function +) const +{ + shared_ptr const& compiler = getContract(_contractName).compiler; + if (!compiler) + return 0; + eth::AssemblyItem tag = compiler->getFunctionEntryLabel(_function); + if (tag.type() == eth::UndefinedItem) + return 0; + eth::AssemblyItems const& items = compiler->getRuntimeAssemblyItems(); + for (size_t i = 0; i < items.size(); ++i) + if (items.at(i).type() == eth::Tag && items.at(i).data() == tag.data()) + return i; + return 0; +} + bytes CompilerStack::staticCompile(std::string const& _sourceCode, bool _optimize) { CompilerStack stack; diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 0bc109a26..6be45aec1 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -48,6 +48,7 @@ namespace solidity // forward declarations class Scanner; class ContractDefinition; +class FunctionDefinition; class SourceUnit; class Compiler; class GlobalContext; @@ -131,6 +132,13 @@ public: /// does not exist. ContractDefinition const& getContractDefinition(std::string const& _contractName) const; + /// @returns the offset of the entry point of the given function into the list of assembly items + /// or zero if it is not found or does not exist. + size_t getFunctionEntryPoint( + std::string const& _contractName, + FunctionDefinition const& _function + ) const; + /// Compile the given @a _sourceCode to bytecode. If a scanner is provided, it is used for /// scanning the source code - this is useful for printing exception information. static bytes staticCompile(std::string const& _sourceCode, bool _optimize = false); diff --git a/libsolidity/GasEstimator.cpp b/libsolidity/GasEstimator.cpp index 18e3a5ca3..01219a65b 100644 --- a/libsolidity/GasEstimator.cpp +++ b/libsolidity/GasEstimator.cpp @@ -30,6 +30,7 @@ #include #include #include +#include using namespace std; using namespace dev; @@ -130,20 +131,45 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( { auto state = make_shared(); - ExpressionClasses& classes = state->expressionClasses(); - using Id = ExpressionClasses::Id; - using Ids = vector; - Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::sha3(_signature))))); - Id calldata = classes.find(eth::Instruction::CALLDATALOAD, Ids{classes.find(u256(0))}); - classes.forceEqual(hashValue, eth::Instruction::DIV, Ids{ - calldata, - classes.find(u256(1) << (8 * 28)) - }); + if (!_signature.empty()) + { + ExpressionClasses& classes = state->expressionClasses(); + using Id = ExpressionClasses::Id; + using Ids = vector; + Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::sha3(_signature))))); + Id calldata = classes.find(eth::Instruction::CALLDATALOAD, Ids{classes.find(u256(0))}); + classes.forceEqual(hashValue, eth::Instruction::DIV, Ids{ + calldata, + classes.find(u256(1) << (8 * 28)) + }); + } PathGasMeter meter(_items); return meter.estimateMax(0, state); } +GasEstimator::GasConsumption GasEstimator::functionalEstimation( + AssemblyItems const& _items, + size_t const& _offset, + FunctionDefinition const& _function +) +{ + auto state = make_shared(); + + unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); + if (parametersSize > 16) + return GasConsumption::infinite(); + + // Store an invalid return value on the stack, so that the path estimator breaks upon reaching + // the return jump. + AssemblyItem invalidTag(PushTag, u256(-0x10)); + state->feedItem(invalidTag, true); + if (parametersSize > 0) + state->feedItem(eth::swapInstruction(parametersSize)); + + return PathGasMeter(_items).estimateMax(_offset, state); +} + set GasEstimator::finestNodesAtLocation( vector const& _roots ) diff --git a/libsolidity/GasEstimator.h b/libsolidity/GasEstimator.h index 32e95fac4..4020d60b3 100644 --- a/libsolidity/GasEstimator.h +++ b/libsolidity/GasEstimator.h @@ -65,6 +65,15 @@ public: std::string const& _signature = "" ); + /// @returns the estimated gas consumption by the given function which starts at the given + /// offset into the list of assembly items. + /// @note this does not work correctly for recursive functions. + static GasConsumption functionalEstimation( + eth::AssemblyItems const& _items, + size_t const& _offset, + FunctionDefinition const& _function + ); + private: /// @returns the set of AST nodes which are the finest nodes at their location. static std::set finestNodesAtLocation(std::vector const& _roots); diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index c86938f81..8129f60c5 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -258,18 +258,38 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) { Gas gas = GasEstimator::functionalEstimation(*items); u256 bytecodeSize(m_compiler->getRuntimeBytecode(_contract).size()); - cout << "[construction]:\t"; - cout << gas << " + " << (bytecodeSize * eth::c_createDataGas) << " = "; + cout << "construction:" << endl; + cout << " " << gas << " + " << (bytecodeSize * eth::c_createDataGas) << " = "; gas += bytecodeSize * eth::c_createDataGas; cout << gas << endl; } if (eth::AssemblyItems const* items = m_compiler->getRuntimeAssemblyItems(_contract)) - for (auto it: m_compiler->getContractDefinition(_contract).getInterfaceFunctions()) + { + ContractDefinition const& contract = m_compiler->getContractDefinition(_contract); + cout << "external:" << endl; + for (auto it: contract.getInterfaceFunctions()) { string sig = it.second->externalSignature(); GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); - cout << sig << ":\t" << gas << endl; + cout << " " << sig << ":\t" << gas << endl; } + cout << "internal:" << endl; + for (auto const& it: contract.getDefinedFunctions()) + { + if (it->isPartOfExternalInterface() || it->isConstructor()) + continue; + size_t entry = m_compiler->getFunctionEntryPoint(_contract, *it); + GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); + if (entry > 0) + gas = GasEstimator::functionalEstimation(*items, entry, *it); + FunctionType type(*it); + cout << " " << it->getName() << "("; + auto end = type.getParameterTypes().end(); + for (auto it = type.getParameterTypes().begin(); it != end; ++it) + cout << (*it)->toString() << (it + 1 == end ? "" : ","); + cout << "):\t" << gas << endl; + } + } } bool CommandLineInterface::parseArguments(int argc, char** argv) From c58d5bb7dd6aa5ef6f9a165ffa8cd204106accac Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 26 May 2015 11:29:41 +0200 Subject: [PATCH 7/8] Removed redundant std. --- libevmasm/GasMeter.cpp | 2 +- test/libsolidity/GasMeter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 650bd6e28..4e5289e38 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -34,7 +34,7 @@ GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption co if (isInfinite) return *this; bigint v = bigint(value) + _other.value; - if (v > std::numeric_limits::max()) + if (v > numeric_limits::max()) *this = infinite(); else value = u256(v); diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index c09849c00..5f4426548 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -71,7 +71,7 @@ public: /// Compares the gas computed by PathGasMeter for the given signature (but unknown arguments) /// against the actual gas usage computed by the VM on the given set of argument variants. - void testRunTimeGas(std::string const& _sig, vector _argumentVariants) + void testRunTimeGas(string const& _sig, vector _argumentVariants) { u256 gasUsed = 0; FixedHash<4> hash(dev::sha3(_sig)); From 93c8024ecc5764fd5d3c18106a91ad2205a6ab49 Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 26 May 2015 13:57:13 +0200 Subject: [PATCH 8/8] Bug fix: - debug: clear local variables list when getting out of a function. - small ui changes. --- mix/ClientModel.cpp | 3 +++ mix/qml/DebuggerPaneStyle.qml | 2 ++ mix/qml/QIntTypeView.qml | 14 +++++++------- mix/qml/StructView.qml | 15 +++++++++++++++ mix/qml/VariablesView.qml | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 6b5242084..238fa5372 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -484,7 +484,10 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) if (!functionName.isEmpty() && ((prevInstruction.getJumpType() == AssemblyItem::JumpType::IntoFunction) || solCallStack.empty())) solCallStack.push_front(QVariant::fromValue(functionName)); else if (prevInstruction.getJumpType() == AssemblyItem::JumpType::OutOfFunction && !solCallStack.empty()) + { solCallStack.pop_front(); + solLocals.clear(); + } } //format solidity context values diff --git a/mix/qml/DebuggerPaneStyle.qml b/mix/qml/DebuggerPaneStyle.qml index db8bbe253..0087b466f 100644 --- a/mix/qml/DebuggerPaneStyle.qml +++ b/mix/qml/DebuggerPaneStyle.qml @@ -9,6 +9,8 @@ QtObject { property QtObject general: QtObject { property int basicFontSize: absoluteSize(-2) + property string basicColor: "#4a4a4a" + property string basicFont: "monospace" property int dataDumpFontSize: absoluteSize(-3) } } diff --git a/mix/qml/QIntTypeView.qml b/mix/qml/QIntTypeView.qml index eacef74e8..a3c67aafc 100644 --- a/mix/qml/QIntTypeView.qml +++ b/mix/qml/QIntTypeView.qml @@ -5,24 +5,24 @@ Item property alias value: textinput.text property alias readOnly: textinput.readOnly id: editRoot - height: 20 width: readOnly ? textinput.implicitWidth : 150 - SourceSansProBold - { - id: boldFont + DebuggerPaneStyle { + id: dbgStyle } Rectangle { anchors.fill: parent radius: 4 TextInput { + anchors.verticalCenter: parent.verticalCenter id: textinput - text: value - anchors.fill: parent - font.family: boldFont.name + font.family: dbgStyle.general.basicFont clip: true selectByMouse: true + text: value + font.pointSize: dbgStyle.general.basicFontSize + color: dbgStyle.general.basicColor } } } diff --git a/mix/qml/StructView.qml b/mix/qml/StructView.qml index cb38ba5ed..029fd162d 100644 --- a/mix/qml/StructView.qml +++ b/mix/qml/StructView.qml @@ -13,6 +13,11 @@ Column property string context Layout.fillWidth: true spacing: 0 + + DebuggerPaneStyle { + id: dbgStyle + } + Repeater { id: repeater @@ -29,6 +34,9 @@ Column id: typeLabel text: modelData.type.name anchors.verticalCenter: parent.verticalCenter + font.family: dbgStyle.general.basicFont + color: dbgStyle.general.basicColor + font.pointSize: dbgStyle.general.basicFontSize } DefaultLabel { @@ -36,6 +44,9 @@ Column id: nameLabel text: modelData.name anchors.verticalCenter: parent.verticalCenter + font.family: dbgStyle.general.basicFont + color: dbgStyle.general.basicColor + font.pointSize: dbgStyle.general.basicFontSize } DefaultLabel { @@ -43,7 +54,11 @@ Column id: equalLabel text: "=" anchors.verticalCenter: parent.verticalCenter + font.family: dbgStyle.general.basicFont + color: dbgStyle.general.basicColor + font.pointSize: dbgStyle.general.basicFontSize } + Loader { id: typeLoader diff --git a/mix/qml/VariablesView.qml b/mix/qml/VariablesView.qml index 6603bd24e..aec629853 100644 --- a/mix/qml/VariablesView.qml +++ b/mix/qml/VariablesView.qml @@ -18,7 +18,7 @@ DebugInfoList property alias members: typeLoader.members; property alias value: typeLoader.value; anchors.fill: parent - anchors.rightMargin: 8 + anchors.leftMargin: 10 StructView { id: typeLoader