From 2d2efc070a6239a76e80e2d452f1f4fad4b20985 Mon Sep 17 00:00:00 2001
From: yann300 <yann.levreau@gmail.com>
Date: Tue, 12 May 2015 17:52:03 +0200
Subject: [PATCH] Gas estimation

---
 mix/CodeModel.cpp             | 49 +++++++++++++++++++++++++++++++++++
 mix/CodeModel.h               | 25 ++++++++++++++++++
 mix/qml/CodeEditorView.qml    |  4 +++
 mix/qml/WebCodeEditor.qml     |  8 ++++++
 mix/qml/html/cm/solarized.css |  4 +++
 mix/qml/html/codeeditor.js    | 23 +++++++++++++++-
 6 files changed, 112 insertions(+), 1 deletion(-)

diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp
index 636a665e6..ecdb46be3 100644
--- a/mix/CodeModel.cpp
+++ b/mix/CodeModel.cpp
@@ -33,6 +33,8 @@
 #include <libsolidity/CompilerStack.h>
 #include <libsolidity/SourceReferenceFormatter.h>
 #include <libsolidity/InterfaceHandler.h>
+#include <libsolidity/StructuralGasEstimator.h>
+#include <libsolidity/SourceReferenceFormatter.h>
 #include <libevmcore/Instruction.h>
 #include <libethcore/CommonJS.h>
 #include "QContractDefinition.h"
@@ -41,6 +43,7 @@
 #include "CodeHighlighter.h"
 #include "FileIo.h"
 #include "CodeModel.h"
+#include "QBigInt.h"
 
 using namespace dev::mix;
 
@@ -50,6 +53,7 @@ const std::set<std::string> c_predefinedContracts =
 
 namespace
 {
+using namespace dev::eth;
 using namespace dev::solidity;
 
 class CollectLocalsVisitor: public ASTConstVisitor
@@ -185,6 +189,7 @@ CodeModel::CodeModel():
 	qRegisterMetaType<QContractDefinition*>("QContractDefinition*");
 	qRegisterMetaType<QFunctionDefinition*>("QFunctionDefinition*");
 	qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*");
+	//qRegisterMetaType<GasMap>("GasMap");
 	qmlRegisterType<QFunctionDefinition>("org.ethereum.qml", 1, 0, "QFunctionDefinition");
 	qmlRegisterType<QVariableDeclaration>("org.ethereum.qml", 1, 0, "QVariableDeclaration");
 }
@@ -292,6 +297,7 @@ void CodeModel::runCompilationJob(int _jobId)
 			}
 		}
 		cs.compile(false);
+		gasEstimation(cs);
 		collectContracts(cs, sourceNames);
 	}
 	catch (dev::Exception const& _exception)
@@ -314,6 +320,49 @@ void CodeModel::runCompilationJob(int _jobId)
 	emit stateChanged();
 }
 
+void CodeModel::gasEstimation(solidity::CompilerStack const& _cs)
+{
+	m_gasCostsMaps.clear();
+	for (std::string n: _cs.getContractNames())
+	{
+
+		ContractDefinition const& contractDefinition = _cs.getContractDefinition(n);
+		QString sourceName = QString::fromStdString(*contractDefinition.getLocation().sourceName);
+
+		if (!m_gasCostsMaps.contains(sourceName))
+			m_gasCostsMaps.insert(sourceName, QVariantList());
+
+		if (!contractDefinition.isFullyImplemented())
+			continue;
+		dev::solidity::SourceUnit const& sourceUnit = _cs.getAST(*contractDefinition.getLocation().sourceName);
+		AssemblyItems const* items = _cs.getRuntimeAssemblyItems(n);
+
+		StructuralGasEstimator estimator;
+		std::map<ASTNode const*, GasMeter::GasConsumption> gasCosts = estimator.breakToStatementLevel(estimator.performEstimation(*items, std::vector<ASTNode const*>({&sourceUnit})), {&sourceUnit});
+
+		for (auto gasIte = gasCosts.begin(); gasIte != gasCosts.end(); ++gasIte)
+		{
+			SourceLocation location = gasIte->first->getLocation();
+			GasMeter::GasConsumption cost = gasIte->second;
+			GasMap* gas = new GasMap(location.start, location.end, (new QBigInt(cost.value))->value());
+			m_gasCostsMaps.find(sourceName).value().push_back(QVariant::fromValue(gas));
+		}
+	}
+}
+
+QVariantList CodeModel::gasCostByDocumentId(QString const& _documentId) const
+{
+	if (m_gasCostsMaps.contains(_documentId))
+	{
+		auto sourceMapIter = m_gasCostsMaps.find(_documentId);
+		int gg = sourceMapIter.value().size();
+		Q_UNUSED(gg);
+		return sourceMapIter.value();
+	}
+	else
+		return QVariantList();
+}
+
 void CodeModel::collectContracts(dev::solidity::CompilerStack const& _cs, std::vector<std::string> const& _sourceNames)
 {
 	Guard pl(x_pendingContracts);
diff --git a/mix/CodeModel.h b/mix/CodeModel.h
index 3f713a17b..a1a2c29ec 100644
--- a/mix/CodeModel.h
+++ b/mix/CodeModel.h
@@ -32,6 +32,7 @@
 #include <libdevcore/Guards.h>
 #include <libevmasm/Assembly.h>
 #include "SolidityType.h"
+#include "QBigInt.h"
 
 class QTextDocument;
 
@@ -126,7 +127,24 @@ struct SourceMap
 	LocationMap functions;
 };
 
+class GasMap: public QObject
+{
+	Q_OBJECT
+
+	Q_PROPERTY(int start MEMBER m_start CONSTANT)
+	Q_PROPERTY(int end MEMBER m_end CONSTANT)
+	Q_PROPERTY(QString gas MEMBER m_gas CONSTANT)
+
+public:
+	GasMap(int _start, int _end, QString _gas): m_start(_start), m_end(_end), m_gas(_gas) {}
+
+	int m_start;
+	int m_end;
+	QString m_gas;
+};
+
 using SourceMaps = QMap<QString, SourceMap>; //by source id
+using GasCostsMaps = QMap<QString, QVariantList/* QList<GasMap>*/>; //gas cost by contract name
 
 /// Code compilation model. Compiles contracts in background an provides compiled contract data
 class CodeModel: public QObject
@@ -166,6 +184,10 @@ public:
 	bool isContractOrFunctionLocation(dev::SourceLocation const& _location);
 	/// Get funciton name by location
 	QString resolveFunctionName(dev::SourceLocation const& _location);
+	/// Gas estimation for compiled sources
+	void gasEstimation(solidity::CompilerStack const& _cs);
+	/// Gas cost by doc id
+	Q_INVOKABLE QVariantList gasCostByDocumentId(QString const& _documentId) const;
 
 signals:
 	/// Emited on compilation state change
@@ -201,6 +223,7 @@ private:
 	mutable dev::Mutex x_contractMap;
 	ContractMap m_contractMap;
 	SourceMaps m_sourceMaps;
+	GasCostsMaps m_gasCostsMaps;
 	std::unique_ptr<CodeHighlighterSettings> m_codeHighlighterSettings;
 	QThread m_backgroundThread;
 	BackgroundWorker m_backgroundWorker;
@@ -214,3 +237,5 @@ private:
 }
 
 }
+
+//Q_DECLARE_METATYPE(dev::mix::GasMap)
diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml
index e4d62ed81..08bb072f5 100644
--- a/mix/qml/CodeEditorView.qml
+++ b/mix/qml/CodeEditorView.qml
@@ -177,6 +177,10 @@ Item {
 		}
 		onCompilationComplete: {
 			sourceInError = "";
+			var gasCosts = codeModel.gasCostByDocumentId(currentDocumentId);
+			var editor = getEditor(currentDocumentId);
+			if (editor)
+				editor.displayGasCosts(gasCosts);
 		}
 	}
 
diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml
index 38f2327b1..a2602304a 100644
--- a/mix/qml/WebCodeEditor.qml
+++ b/mix/qml/WebCodeEditor.qml
@@ -83,6 +83,14 @@ Item {
 			editorBrowser.runJavaScript("setFontSize(" + size + ")", function(result) {});
 	}
 
+	function displayGasCosts(gasCosts) {
+
+		//console.log(gasCosts);
+		//console.log(JSON.stringify(gasCosts));
+		if (initialized && editorBrowser)
+			editorBrowser.runJavaScript("displayGasCosts('" + JSON.stringify(gasCosts) + "')", function(result) {});
+	}
+
 	Clipboard
 	{
 		id: clipboard
diff --git a/mix/qml/html/cm/solarized.css b/mix/qml/html/cm/solarized.css
index b8cede806..046042450 100644
--- a/mix/qml/html/cm/solarized.css
+++ b/mix/qml/html/cm/solarized.css
@@ -189,3 +189,7 @@ view-port
 }
 
 span.CodeMirror-selectedtext { color: #586e75 !important; }
+
+.gasCost {
+   background: #b58900 !important;
+}
diff --git a/mix/qml/html/codeeditor.js b/mix/qml/html/codeeditor.js
index d25fbd091..cc5e9a28c 100644
--- a/mix/qml/html/codeeditor.js
+++ b/mix/qml/html/codeeditor.js
@@ -3,7 +3,7 @@ var editor = CodeMirror(document.body, {
 							//styleActiveLine: true,
 							matchBrackets: true,
 							autofocus: true,
-							gutters: ["CodeMirror-linenumbers", "breakpoints"],
+							gutters: ["CodeMirror-linenumbers", "breakpoints", "gasCost"],
 							autoCloseBrackets: true,
 							styleSelectedText: true
 						});
@@ -203,5 +203,26 @@ setFontSize = function(size)
 	editor.refresh();
 }
 
+makeGasCostMarker = function(value) {
+	var marker = document.createElement("div");
+	marker.style.color = "#822";
+	marker.innerHTML = value;
+	return marker;
+};
+
+displayGasCosts = function(gasCosts)
+{
+	gasCosts = JSON.parse(gasCosts);
+	for (var i in gasCosts)
+	{
+		var line = editor.posFromIndex(gasCosts[i].start);
+		console.log("___________")
+		console.log(line.line);
+		console.log(gasCosts[i].start);
+		//editor.setGutterMarker(line.line, "gasCost", makeGasCostMarker(gasCosts[i].gas));
+		editor.markText(editor.posFromIndex(gasCosts[i].start), editor.posFromIndex(gasCosts[i].end), { className: "gasCost" });
+	}
+}
+
 editor.setOption("extraKeys", extraKeys);