From e6221108b601938d6165cc1a0583d70c30e6e364 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 10 Apr 2017 14:00:24 +0100 Subject: Move gasEstimate into CompilerStack --- libsolidity/interface/CompilerStack.cpp | 85 +++++++++++++++++++++++++++++++++ libsolidity/interface/CompilerStack.h | 3 ++ 2 files changed, 88 insertions(+) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 9ad28573..0fddd505 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -841,3 +842,87 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con } return ret; } + +namespace +{ + +Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) +{ + if (_gas.isInfinite || _gas.value > std::numeric_limits::max()) + return Json::Value(Json::nullValue); + else + return Json::Value(Json::LargestUInt(_gas.value)); +} + +} + +Json::Value CompilerStack::gasEstimates(string const& _contractName) const +{ + if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName)) + return Json::Value(); + + using Gas = GasEstimator::GasConsumption; + Json::Value output(Json::objectValue); + + if (eth::AssemblyItems const* items = assemblyItems(_contractName)) + { + Gas executionGas = GasEstimator::functionalEstimation(*items); + u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); + Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; + + Json::Value creation(Json::objectValue); + creation["codeDepositCost"] = gasToJson(codeDepositGas); + creation["executionCost"] = gasToJson(executionGas); + /// TODO: implement + overload to avoid the need of += + executionGas += codeDepositGas; + creation["totalCost"] = gasToJson(executionGas); + output["creation"] = creation; + } + + if (eth::AssemblyItems const* items = runtimeAssemblyItems(_contractName)) + { + /// External functions + ContractDefinition const& contract = contractDefinition(_contractName); + Json::Value externalFunctions(Json::objectValue); + for (auto it: contract.interfaceFunctions()) + { + string sig = it.second->externalSignature(); + externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); + } + + if (contract.fallbackFunction()) + /// This needs to be set to an invalid signature in order to trigger the fallback, + /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound. + /// An empty string ("") would work to trigger the shortcut only. + externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); + + if (!externalFunctions.empty()) + output["external"] = externalFunctions; + + /// Internal functions + Json::Value internalFunctions(Json::objectValue); + for (auto const& it: contract.definedFunctions()) + { + if (it->isPartOfExternalInterface() || it->isConstructor()) + continue; + + size_t entry = functionEntryPoint(_contractName, *it); + GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); + if (entry > 0) + gas = GasEstimator::functionalEstimation(*items, entry, *it); + + FunctionType type(*it); + string sig = it->name() + "("; + auto paramTypes = type.parameterTypes(); + for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) + sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); + sig += ")"; + internalFunctions[sig] = gasToJson(gas); + } + + if (!internalFunctions.empty()) + output["internal"] = internalFunctions; + } + + return output; +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 9c37eead..a34a34bb 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -173,6 +173,9 @@ public: std::string const& onChainMetadata(std::string const& _contractName) const; void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } + /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions + Json::Value gasEstimates(std::string const& _contractName) const; + /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; /// @returns the parsed source unit with the supplied name. -- cgit From d90fd439e2e96551018b4f90ec073cf7e9b6e4d9 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 10 Apr 2017 14:52:05 +0100 Subject: Use new gasEstimate in jsonCompiler --- solc/jsonCompiler.cpp | 63 ++++++++++----------------------------------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 065716ea..b465d8db 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -58,61 +58,22 @@ Json::Value functionHashes(ContractDefinition const& _contract) return functionHashes; } -Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) -{ - if (_gas.isInfinite || _gas.value > std::numeric_limits::max()) - return Json::Value(Json::nullValue); - else - return Json::Value(Json::LargestUInt(_gas.value)); -} - Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) { - Json::Value gasEstimates(Json::objectValue); - using Gas = GasEstimator::GasConsumption; - if (!_compiler.assemblyItems(_contract) && !_compiler.runtimeAssemblyItems(_contract)) - return gasEstimates; - if (eth::AssemblyItems const* items = _compiler.assemblyItems(_contract)) - { - Gas gas = GasEstimator::functionalEstimation(*items); - u256 bytecodeSize(_compiler.runtimeObject(_contract).bytecode.size()); - Json::Value creationGas(Json::arrayValue); - creationGas[0] = gasToJson(gas); - creationGas[1] = gasToJson(bytecodeSize * eth::GasCosts::createDataGas); - gasEstimates["creation"] = creationGas; - } - if (eth::AssemblyItems const* items = _compiler.runtimeAssemblyItems(_contract)) + Json::Value estimates = _compiler.gasEstimates(_contract); + Json::Value output(Json::objectValue); + + if (estimates["creation"].isObject()) { - ContractDefinition const& contract = _compiler.contractDefinition(_contract); - Json::Value externalFunctions(Json::objectValue); - for (auto it: contract.interfaceFunctions()) - { - string sig = it.second->externalSignature(); - externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); - } - if (contract.fallbackFunction()) - externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); - gasEstimates["external"] = externalFunctions; - Json::Value internalFunctions(Json::objectValue); - for (auto const& it: contract.definedFunctions()) - { - if (it->isPartOfExternalInterface() || it->isConstructor()) - continue; - size_t entry = _compiler.functionEntryPoint(_contract, *it); - GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); - if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); - FunctionType type(*it); - string sig = it->name() + "("; - auto paramTypes = type.parameterTypes(); - for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) - sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); - sig += ")"; - internalFunctions[sig] = gasToJson(gas); - } - gasEstimates["internal"] = internalFunctions; + Json::Value creation(Json::arrayValue); + creation[0] = estimates["creation"]["executionCost"]; + creation[1] = estimates["creation"]["codeDepositCost"]; + output["creation"] = creation; } - return gasEstimates; + output["external"] = estimates["external"]; + output["internal"] = estimates["internal"]; + + return output; } string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback) -- cgit From 328f2b0a8efa41d0343b4df022ce1106a5984fb4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 10 Apr 2017 14:52:13 +0100 Subject: Use new gasEstimate in CLI --- solc/CommandLineInterface.cpp | 72 +++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index f0b73152..90cc4769 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -315,49 +315,55 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co void CommandLineInterface::handleGasEstimation(string const& _contract) { - using Gas = GasEstimator::GasConsumption; - if (!m_compiler->assemblyItems(_contract) && !m_compiler->runtimeAssemblyItems(_contract)) - return; + Json::Value estimates = m_compiler->gasEstimates(_contract); cout << "Gas estimation:" << endl; - if (eth::AssemblyItems const* items = m_compiler->assemblyItems(_contract)) + + if (estimates["creation"].isObject()) { - Gas gas = GasEstimator::functionalEstimation(*items); - u256 bytecodeSize(m_compiler->runtimeObject(_contract).bytecode.size()); + Json::Value creation = estimates["creation"]; cout << "construction:" << endl; - cout << " " << gas << " + " << (bytecodeSize * eth::GasCosts::createDataGas) << " = "; - gas += bytecodeSize * eth::GasCosts::createDataGas; - cout << gas << endl; + if (creation["executionCost"].isNull()) + cout << " infinite"; + else + cout << " " << creation["executionCost"]; + if (creation["codeDepositCost"].isNull()) + cout << " + infinite"; + else + cout << " + " << creation["codeDepositCost"]; + if (creation["totalCost"].isNull()) + cout << " = infinite"; + else + cout << " = " << creation["totalCost"] << endl; } - if (eth::AssemblyItems const* items = m_compiler->runtimeAssemblyItems(_contract)) + + if (estimates["external"].isObject()) { - ContractDefinition const& contract = m_compiler->contractDefinition(_contract); + Json::Value externalFunctions = estimates["external"]; cout << "external:" << endl; - for (auto it: contract.interfaceFunctions()) - { - string sig = it.second->externalSignature(); - GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); - cout << " " << sig << ":\t" << gas << endl; - } - if (contract.fallbackFunction()) + for (auto const& name: externalFunctions.getMemberNames()) { - GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID"); - cout << " fallback:\t" << gas << endl; + if (name.empty()) + cout << " fallback:\t"; + else + cout << " " << name << ":\t"; + if (externalFunctions[name].isNull()) + cout << "infinite" << endl; + else + cout << externalFunctions[name] << endl; } + } + + if (estimates["internal"].isObject()) + { + Json::Value internalFunctions = estimates["internal"]; cout << "internal:" << endl; - for (auto const& it: contract.definedFunctions()) + for (auto const& name: internalFunctions.getMemberNames()) { - if (it->isPartOfExternalInterface() || it->isConstructor()) - continue; - size_t entry = m_compiler->functionEntryPoint(_contract, *it); - GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); - if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); - FunctionType type(*it); - cout << " " << it->name() << "("; - auto paramTypes = type.parameterTypes(); - for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) - cout << (*it)->toString() << (it + 1 == paramTypes.end() ? "" : ","); - cout << "):\t" << gas << endl; + cout << " " << name << ":\t"; + if (internalFunctions[name].isNull()) + cout << "infinite" << endl; + else + cout << internalFunctions[name] << endl; } } } -- cgit From 3fbb48bd46b613f83c6918e3b43068c72a214965 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 10 Apr 2017 14:59:30 +0100 Subject: Exclude fallback function from the internal functions in estimateGas --- libsolidity/interface/CompilerStack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 0fddd505..ea648709 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -903,7 +903,8 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const Json::Value internalFunctions(Json::objectValue); for (auto const& it: contract.definedFunctions()) { - if (it->isPartOfExternalInterface() || it->isConstructor()) + /// Exclude externally visible functions, constructor and the fallback function + if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty()) continue; size_t entry = functionEntryPoint(_contractName, *it); -- cgit From fe4fccaaf21dbb7cbfed9b758a3d8f12f979c6dc Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 11 Apr 2017 10:01:24 +0100 Subject: The gasEstimates objects must always be present in the jsonCompiler even if empty (backwards compat) --- solc/jsonCompiler.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index b465d8db..accbc13c 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -70,8 +70,10 @@ Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) creation[1] = estimates["creation"]["codeDepositCost"]; output["creation"] = creation; } - output["external"] = estimates["external"]; - output["internal"] = estimates["internal"]; + else + output["creation"] = Json::objectValue; + output["external"] = estimates.get("external", Json::objectValue); + output["internal"] = estimates.get("internal", Json::objectValue); return output; } -- cgit From 54dcb0e11be09caf35e02792d47695685ba1f4cb Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 12 Apr 2017 12:06:01 +0100 Subject: Keep gas values as a string in CompilerStack::gasEstimate --- libsolidity/interface/CompilerStack.cpp | 6 +++--- solc/CommandLineInterface.cpp | 25 +++++-------------------- solc/jsonCompiler.cpp | 29 +++++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index ea648709..92b49cda 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -848,10 +848,10 @@ namespace Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) { - if (_gas.isInfinite || _gas.value > std::numeric_limits::max()) - return Json::Value(Json::nullValue); + if (_gas.isInfinite) + return Json::Value("infinite"); else - return Json::Value(Json::LargestUInt(_gas.value)); + return Json::Value(toString(_gas.value)); } } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 90cc4769..947a2004 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -322,18 +322,9 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) { Json::Value creation = estimates["creation"]; cout << "construction:" << endl; - if (creation["executionCost"].isNull()) - cout << " infinite"; - else - cout << " " << creation["executionCost"]; - if (creation["codeDepositCost"].isNull()) - cout << " + infinite"; - else - cout << " + " << creation["codeDepositCost"]; - if (creation["totalCost"].isNull()) - cout << " = infinite"; - else - cout << " = " << creation["totalCost"] << endl; + cout << " " << creation["executionCost"].asString(); + cout << " + " << creation["codeDepositCost"].asString(); + cout << " = " << creation["totalCost"].asString() << endl; } if (estimates["external"].isObject()) @@ -346,10 +337,7 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) cout << " fallback:\t"; else cout << " " << name << ":\t"; - if (externalFunctions[name].isNull()) - cout << "infinite" << endl; - else - cout << externalFunctions[name] << endl; + cout << externalFunctions[name].asString() << endl; } } @@ -360,10 +348,7 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) for (auto const& name: internalFunctions.getMemberNames()) { cout << " " << name << ":\t"; - if (internalFunctions[name].isNull()) - cout << "infinite" << endl; - else - cout << internalFunctions[name] << endl; + cout << internalFunctions[name].asString() << endl; } } } diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index accbc13c..fd375ce3 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -58,6 +58,27 @@ Json::Value functionHashes(ContractDefinition const& _contract) return functionHashes; } +/// Translates a gas value as a string to a JSON number or null +Json::Value gasToJson(Json::Value const& _value) +{ + if (_value.isObject()) + { + Json::Value ret = Json::objectValue; + for (auto const& sig: _value.getMemberNames()) + ret[sig] = gasToJson(_value[sig]); + return ret; + } + + if (_value == "infinite") + return Json::Value(Json::nullValue); + + u256 value(_value.asString()); + if (value > std::numeric_limits::max()) + return Json::Value(Json::nullValue); + else + return Json::Value(Json::LargestUInt(value)); +} + Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) { Json::Value estimates = _compiler.gasEstimates(_contract); @@ -66,14 +87,14 @@ Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) if (estimates["creation"].isObject()) { Json::Value creation(Json::arrayValue); - creation[0] = estimates["creation"]["executionCost"]; - creation[1] = estimates["creation"]["codeDepositCost"]; + creation[0] = gasToJson(estimates["creation"]["executionCost"]); + creation[1] = gasToJson(estimates["creation"]["codeDepositCost"]); output["creation"] = creation; } else output["creation"] = Json::objectValue; - output["external"] = estimates.get("external", Json::objectValue); - output["internal"] = estimates.get("internal", Json::objectValue); + output["external"] = gasToJson(estimates.get("external", Json::objectValue)); + output["internal"] = gasToJson(estimates.get("internal", Json::objectValue)); return output; } -- cgit