1 /*
2 	This file is part of solidity.
3 
4 	solidity is free software: you can redistribute it and/or modify
5 	it under the terms of the GNU General Public License as published by
6 	the Free Software Foundation, either version 3 of the License, or
7 	(at your option) any later version.
8 
9 	solidity is distributed in the hope that it will be useful,
10 	but WITHOUT ANY WARRANTY; without even the implied warranty of
11 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 	GNU General Public License for more details.
13 
14 	You should have received a copy of the GNU General Public License
15 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 // SPDX-License-Identifier: GPL-3.0
18 
19 #include <test/libsolidity/GasTest.h>
20 #include <test/Common.h>
21 #include <libsolutil/CommonIO.h>
22 #include <libsolutil/JSON.h>
23 #include <liblangutil/SourceReferenceFormatter.h>
24 #include <boost/algorithm/string.hpp>
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <boost/filesystem.hpp>
27 #include <boost/test/unit_test.hpp>
28 #include <boost/throw_exception.hpp>
29 #include <fstream>
30 #include <stdexcept>
31 
32 using namespace solidity::langutil;
33 using namespace solidity::frontend;
34 using namespace solidity::frontend::test;
35 using namespace solidity;
36 using namespace std;
37 using namespace boost::unit_test;
38 
GasTest(string const & _filename)39 GasTest::GasTest(string const& _filename):
40 	TestCase(_filename)
41 {
42 	m_source = m_reader.source();
43 	m_optimise = m_reader.boolSetting("optimize", false);
44 	m_optimiseYul = m_reader.boolSetting("optimize-yul", false);
45 	m_optimiseRuns = m_reader.sizetSetting("optimize-runs", OptimiserSettings{}.expectedExecutionsPerDeployment);
46 	parseExpectations(m_reader.stream());
47 }
48 
parseExpectations(std::istream & _stream)49 void GasTest::parseExpectations(std::istream& _stream)
50 {
51 	map<std::string, std::string>* currentKind = nullptr;
52 	string line;
53 
54 	while (getline(_stream, line))
55 		if (!boost::starts_with(line, "// "))
56 			BOOST_THROW_EXCEPTION(runtime_error("Invalid expectation: expected \"// \"."));
57 		else if (boost::ends_with(line, ":"))
58 		{
59 			string kind = line.substr(3, line.length() - 4);
60 			boost::trim(kind);
61 			currentKind = &m_expectations[move(kind)];
62 		}
63 		else if (!currentKind)
64 			BOOST_THROW_EXCEPTION(runtime_error("No function kind specified. Expected \"creation:\", \"external:\" or \"internal:\"."));
65 		else
66 		{
67 			auto it = line.begin() + 3;
68 			skipWhitespace(it, line.end());
69 			auto functionNameBegin = it;
70 			while (it != line.end() && *it != ':')
71 				++it;
72 			string functionName(functionNameBegin, it);
73 			if (functionName == "fallback")
74 				functionName.clear();
75 			expect(it, line.end(), ':');
76 			skipWhitespace(it, line.end());
77 			if (it == line.end())
78 				BOOST_THROW_EXCEPTION(runtime_error("Invalid expectation: expected gas cost."));
79 			(*currentKind)[functionName] = std::string(it, line.end());
80 		}
81 }
82 
printUpdatedExpectations(ostream & _stream,string const & _linePrefix) const83 void GasTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const
84 {
85 	Json::Value estimates = compiler().gasEstimates(compiler().lastContractName());
86 	for (auto groupIt = estimates.begin(); groupIt != estimates.end(); ++groupIt)
87 	{
88 		_stream << _linePrefix << groupIt.key().asString() << ":" << std::endl;
89 		for (auto it = groupIt->begin(); it != groupIt->end(); ++it)
90 		{
91 			_stream << _linePrefix << "  ";
92 			if (it.key().asString().empty())
93 				_stream << "fallback";
94 			else
95 				_stream << it.key().asString();
96 			_stream << ": " << it->asString() << std::endl;
97 		}
98 	}
99 }
100 
run(ostream & _stream,string const & _linePrefix,bool _formatted)101 TestCase::TestResult GasTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
102 {
103 	string const preamble = "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n";
104 	compiler().reset();
105 	// Prerelease CBOR metadata varies in size due to changing version numbers and build dates.
106 	// This leads to volatile creation cost estimates. Therefore we force the compiler to
107 	// release mode for testing gas estimates.
108 	compiler().setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata);
109 	OptimiserSettings settings = m_optimise ? OptimiserSettings::standard() : OptimiserSettings::minimal();
110 	if (m_optimiseYul)
111 	{
112 		settings.runYulOptimiser = m_optimise;
113 		settings.optimizeStackAllocation = m_optimise;
114 	}
115 	settings.expectedExecutionsPerDeployment = m_optimiseRuns;
116 	compiler().setOptimiserSettings(settings);
117 	compiler().setSources({{"", preamble + m_source}});
118 
119 	if (!compiler().parseAndAnalyze() || !compiler().compile())
120 	{
121 		SourceReferenceFormatter{_stream, compiler(), _formatted, false}
122 			.printErrorInformation(compiler().errors());
123 		return TestResult::FatalError;
124 	}
125 
126 	Json::Value estimateGroups = compiler().gasEstimates(compiler().lastContractName());
127 	if (
128 		m_expectations.size() == estimateGroups.size() &&
129 		boost::all(m_expectations, [&](auto const& expectations) {
130 		auto const& estimates = estimateGroups[expectations.first];
131 		return estimates.size() == expectations.second.size() &&
132 			boost::all(expectations.second, [&](auto const& entry) {
133 				return entry.second == estimates[entry.first].asString();
134 			});
135 		})
136 	)
137 		return TestResult::Success;
138 	else
139 	{
140 		_stream << _linePrefix << "Expected:" << std::endl;
141 		for (auto const& expectations: m_expectations)
142 		{
143 			_stream << _linePrefix << "  " << expectations.first << ":" << std::endl;
144 			for (auto const& entry: expectations.second)
145 				_stream << _linePrefix
146 					<< "    "
147 					<< (entry.first.empty() ? "fallback" : entry.first)
148 					<< ": "
149 					<< entry.second
150 					<< std::endl;
151 		}
152 		_stream << _linePrefix << "Obtained:" << std::endl;
153 		printUpdatedExpectations(_stream, _linePrefix + "  ");
154 		return TestResult::Failure;
155 	}
156 }
157 
printSource(ostream & _stream,string const & _linePrefix,bool) const158 void GasTest::printSource(ostream& _stream, string const& _linePrefix, bool) const
159 {
160 	string line;
161 	istringstream input(m_source);
162 	while (getline(input, line))
163 		_stream << _linePrefix << line << std::endl;
164 }
165