Allow overwriting the release flag for tests and adjust test suite to CBOR metadata.

This commit is contained in:
Daniel Kirchner 2019-05-06 14:31:49 +02:00
parent e121c8f3c7
commit a6a28ac475
18 changed files with 156 additions and 187 deletions

View File

@ -1177,7 +1177,7 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes());
if (_experimentalMode)
encoder.pushBool("experimental", true);
if (VersionIsRelease)
if (m_release)
encoder.pushBytes("solc", VersionCompactBytes);
else
encoder.pushString("solc", VersionStringStrict);

View File

@ -25,6 +25,7 @@
#include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/Version.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
@ -261,6 +262,8 @@ public:
/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
Json::Value gasEstimates(std::string const& _contractName) const;
/// Overwrites the release/prerelease flag. Should only be used for testing.
void overwriteReleaseFlag(bool release) { m_release = release; }
private:
/// The state per source unit. Filled gradually during parsing.
struct Source
@ -333,7 +336,7 @@ private:
std::string createMetadata(Contract const& _contract) const;
/// @returns the metadata CBOR for the given serialised metadata JSON.
static bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
/// @returns the computer source mapping string.
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
@ -382,6 +385,7 @@ private:
langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false;
State m_stackState = Empty;
bool m_release = VersionIsRelease;
};
}

View File

@ -1,17 +0,0 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"outputSelection":
{
"*": { "*": ["evm.gasEstimates"] }
}
}
}

View File

@ -1 +0,0 @@
{"contracts":{"A":{"C":{"evm":{"gasEstimates":{"creation":{"codeDepositCost":"19800","executionCost":"75","totalCost":"19875"},"external":{"f()":"122"}}}}}},"sources":{"A":{"id":0}}}

View File

@ -66,20 +66,21 @@ BOOST_AUTO_TEST_CASE(string_storage)
}
}
)";
m_compiler.overwriteReleaseFlag(true);
compileAndRun(sourceCode);
if (Options::get().evmVersion() <= EVMVersion::byzantium())
CHECK_GAS(133899, 130591, 100);
CHECK_GAS(136247, 132939, 100);
// This is only correct on >=Constantinople.
else if (Options::get().useABIEncoderV2)
{
if (Options::get().optimizeYul)
CHECK_GAS(151283, 128285, 100);
CHECK_GAS(153631, 130633, 100);
else
CHECK_GAS(151283, 136003, 100);
CHECK_GAS(153631, 138351, 100);
}
else
CHECK_GAS(126689, 120159, 100);
CHECK_GAS(129037, 122500, 100);
if (Options::get().evmVersion() >= EVMVersion::byzantium())
{
callContractFunction("f()");

View File

@ -34,22 +34,6 @@ using namespace std;
namespace fs = boost::filesystem;
using namespace boost::unit_test;
namespace
{
u256 parseGasCost(string::iterator& _it, string::iterator _end)
{
if (_it == _end || !isdigit(*_it))
throw runtime_error("Invalid test expectation: expected gas cost.");
auto begin = _it;
while (_it != _end && isdigit(*_it))
++_it;
return u256(std::string(begin, _it));
}
}
GasTest::GasTest(string const& _filename)
{
ifstream file(_filename);
@ -83,42 +67,28 @@ GasTest::GasTest(string const& _filename)
void GasTest::parseExpectations(std::istream& _stream)
{
std::map<std::string, std::string>* currentKind = nullptr;
std::string line;
map<std::string, std::string>* currentKind = nullptr;
string line;
while (getline(_stream, line))
if (boost::starts_with(line, "// creation:"))
if (!boost::starts_with(line, "// "))
BOOST_THROW_EXCEPTION(runtime_error("Invalid expectation: expected \"// \"."));
else if (boost::ends_with(line, ":"))
{
auto it = line.begin() + 12;
skipWhitespace(it, line.end());
m_creationCost.executionCost = parseGasCost(it, line.end());
skipWhitespace(it, line.end());
if (*it++ != '+')
BOOST_THROW_EXCEPTION(runtime_error("Invalid expectation: expected \"+\"-"));
skipWhitespace(it, line.end());
m_creationCost.codeDepositCost = parseGasCost(it, line.end());
skipWhitespace(it, line.end());
if (*it++ != '=')
BOOST_THROW_EXCEPTION(runtime_error("Invalid expectation: expected \"+\"-"));
skipWhitespace(it, line.end());
m_creationCost.totalCost = parseGasCost(it, line.end());
string kind = line.substr(3, line.length() - 4);
boost::trim(kind);
currentKind = &m_expectations[move(kind)];
}
else if (line == "// external:")
currentKind = &m_externalFunctionCosts;
else if (line == "// internal:")
currentKind = &m_internalFunctionCosts;
else if (!currentKind)
BOOST_THROW_EXCEPTION(runtime_error("No function kind specified. Expected \"external:\" or \"internal:\"."));
BOOST_THROW_EXCEPTION(runtime_error("No function kind specified. Expected \"creation:\", \"external:\" or \"internal:\"."));
else
{
if (!boost::starts_with(line, "// "))
BOOST_THROW_EXCEPTION(runtime_error("Invalid expectation: expected \"// \"."));
auto it = line.begin() + 3;
skipWhitespace(it, line.end());
auto functionNameBegin = it;
while (it != line.end() && *it != ':')
++it;
std::string functionName(functionNameBegin, it);
string functionName(functionNameBegin, it);
if (functionName == "fallback")
functionName.clear();
expect(it, line.end(), ':');
@ -129,39 +99,32 @@ void GasTest::parseExpectations(std::istream& _stream)
}
}
void GasTest::printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const
void GasTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const
{
Json::Value estimates = compiler().gasEstimates(compiler().lastContractName());
_stream << _linePrefix
<< "creation: "
<< estimates["creation"]["executionCost"].asString()
<< " + "
<< estimates["creation"]["codeDepositCost"].asString()
<< " = "
<< estimates["creation"]["totalCost"].asString()
<< std::endl;
for (auto kind: {"external", "internal"})
if (estimates[kind])
for (auto groupIt = estimates.begin(); groupIt != estimates.end(); ++groupIt)
{
_stream << _linePrefix << groupIt.key().asString() << ":" << std::endl;
for (auto it = groupIt->begin(); it != groupIt->end(); ++it)
{
_stream << _linePrefix << kind << ":" << std::endl;
for (auto it = estimates[kind].begin(); it != estimates[kind].end(); ++it)
{
_stream << _linePrefix << " ";
if (it.key().asString().empty())
_stream << "fallback";
else
_stream << it.key().asString();
_stream << ": " << it->asString() << std::endl;
}
_stream << _linePrefix << " ";
if (it.key().asString().empty())
_stream << "fallback";
else
_stream << it.key().asString();
_stream << ": " << it->asString() << std::endl;
}
}
}
TestCase::TestResult GasTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
{
string const versionPragma = "pragma solidity >=0.0;\n";
compiler().reset();
// Prerelease CBOR metadata varies in size due to changing version numbers and build dates.
// This leads to volatile creation cost estimates. Therefore we force the compiler to
// release mode for testing gas estimates.
compiler().overwriteReleaseFlag(true);
OptimiserSettings settings = m_optimise ? OptimiserSettings::standard() : OptimiserSettings::minimal();
if (m_optimiseYul)
{
@ -180,59 +143,42 @@ TestCase::TestResult GasTest::run(ostream& _stream, string const& _linePrefix, b
return TestResult::FatalError;
}
Json::Value estimates = compiler().gasEstimates(compiler().lastContractName());
auto creation = estimates["creation"];
bool success =
(creation["codeDepositCost"].asString() == toString(m_creationCost.codeDepositCost)) &&
(creation["executionCost"].asString() == toString(m_creationCost.executionCost)) &&
(creation["totalCost"].asString() == toString(m_creationCost.totalCost));
auto check = [&](map<string, string> const& _a, Json::Value const& _b) {
for (auto& entry: _a)
success &= _b[entry.first].asString() == entry.second;
};
check(m_internalFunctionCosts, estimates["internal"]);
check(m_externalFunctionCosts, estimates["external"]);
if (!success)
Json::Value estimateGroups = compiler().gasEstimates(compiler().lastContractName());
if (
m_expectations.size() == estimateGroups.size() &&
boost::all(m_expectations, [&](auto const& expectations) {
auto const& estimates = estimateGroups[expectations.first];
return estimates.size() == expectations.second.size() &&
boost::all(expectations.second, [&](auto const& entry) {
return entry.second == estimates[entry.first].asString();
});
})
)
return TestResult::Success;
else
{
_stream << _linePrefix << "Expected:" << std::endl;
_stream << _linePrefix
<< " creation: "
<< toString(m_creationCost.executionCost)
<< " + "
<< toString(m_creationCost.codeDepositCost)
<< " = "
<< toString(m_creationCost.totalCost)
<< std::endl;
auto printExpected = [&](std::string const& _kind, auto const& _expectations)
for (auto const& expectations: m_expectations)
{
_stream << _linePrefix << " " << _kind << ":" << std::endl;
for (auto const& entry: _expectations)
_stream << _linePrefix << " " << expectations.first << ":" << std::endl;
for (auto const& entry: expectations.second)
_stream << _linePrefix
<< " "
<< (entry.first.empty() ? "fallback" : entry.first)
<< ": "
<< entry.second
<< std::endl;
};
if (!m_externalFunctionCosts.empty())
printExpected("external", m_externalFunctionCosts);
if (!m_internalFunctionCosts.empty())
printExpected("internal", m_internalFunctionCosts);
}
_stream << _linePrefix << "Obtained:" << std::endl;
printUpdatedExpectations(_stream, _linePrefix + " ");
return TestResult::Failure;
}
return success ? TestResult::Success : TestResult::Failure;
}
void GasTest::printSource(ostream& _stream, string const& _linePrefix, bool) const
{
std::string line;
std::istringstream input(m_source);
string line;
istringstream input(m_source);
while (getline(input, line))
_stream << _linePrefix << line << std::endl;
}

View File

@ -48,20 +48,11 @@ public:
private:
void parseExpectations(std::istream& _stream);
struct CreationCost
{
u256 executionCost{0};
u256 codeDepositCost{0};
u256 totalCost{0};
};
bool m_optimise = false;
bool m_optimiseYul = false;
size_t m_optimiseRuns = 200;
std::string m_source;
CreationCost m_creationCost;
std::map<std::string, std::string> m_externalFunctionCosts;
std::map<std::string, std::string> m_internalFunctionCosts;
std::map<std::string, std::map<std::string, std::string>> m_expectations;
};
}

View File

@ -59,25 +59,29 @@ BOOST_AUTO_TEST_CASE(metadata_stamp)
function g(function(uint) external returns (uint) x) public {}
}
)";
CompilerStack compilerStack;
compilerStack.setSources({{"", std::string(sourceCode)}});
compilerStack.setEVMVersion(dev::test::Options::get().evmVersion());
compilerStack.setOptimiserSettings(dev::test::Options::get().optimize);
BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed");
bytes const& bytecode = compilerStack.runtimeObject("test").bytecode;
std::string const& metadata = compilerStack.metadata("test");
BOOST_CHECK(dev::test::isValidMetadata(metadata));
bytes hash = dev::swarmHash(metadata).asBytes();
BOOST_REQUIRE(hash.size() == 32);
auto const cborMetadata = requireParsedCBORMetadata(bytecode);
BOOST_CHECK(cborMetadata.size() == 2);
BOOST_CHECK(cborMetadata.count("solc") == 1);
if (VersionIsRelease)
BOOST_CHECK(cborMetadata.at("solc") == toHex(VersionCompactBytes));
else
BOOST_CHECK(cborMetadata.at("solc") == VersionStringStrict);
BOOST_CHECK(cborMetadata.count("bzzr0") == 1);
BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash));
for (auto release: std::set<bool>{true, VersionIsRelease})
{
CompilerStack compilerStack;
compilerStack.overwriteReleaseFlag(release);
compilerStack.setSources({{"", std::string(sourceCode)}});
compilerStack.setEVMVersion(dev::test::Options::get().evmVersion());
compilerStack.setOptimiserSettings(dev::test::Options::get().optimize);
BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed");
bytes const& bytecode = compilerStack.runtimeObject("test").bytecode;
std::string const& metadata = compilerStack.metadata("test");
BOOST_CHECK(dev::test::isValidMetadata(metadata));
bytes hash = dev::swarmHash(metadata).asBytes();
BOOST_REQUIRE(hash.size() == 32);
auto const cborMetadata = requireParsedCBORMetadata(bytecode);
BOOST_CHECK(cborMetadata.size() == 2);
BOOST_CHECK(cborMetadata.count("solc") == 1);
if (release)
BOOST_CHECK(cborMetadata.at("solc") == toHex(VersionCompactBytes));
else
BOOST_CHECK(cborMetadata.at("solc") == VersionStringStrict);
BOOST_CHECK(cborMetadata.count("bzzr0") == 1);
BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash));
}
}
BOOST_AUTO_TEST_CASE(metadata_stamp_experimental)
@ -90,27 +94,31 @@ BOOST_AUTO_TEST_CASE(metadata_stamp_experimental)
function g(function(uint) external returns (uint) x) public {}
}
)";
CompilerStack compilerStack;
compilerStack.setSources({{"", std::string(sourceCode)}});
compilerStack.setEVMVersion(dev::test::Options::get().evmVersion());
compilerStack.setOptimiserSettings(dev::test::Options::get().optimize);
BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed");
bytes const& bytecode = compilerStack.runtimeObject("test").bytecode;
std::string const& metadata = compilerStack.metadata("test");
BOOST_CHECK(dev::test::isValidMetadata(metadata));
bytes hash = dev::swarmHash(metadata).asBytes();
BOOST_REQUIRE(hash.size() == 32);
auto const cborMetadata = requireParsedCBORMetadata(bytecode);
BOOST_CHECK(cborMetadata.size() == 3);
BOOST_CHECK(cborMetadata.count("solc") == 1);
if (VersionIsRelease)
BOOST_CHECK(cborMetadata.at("solc") == toHex(VersionCompactBytes));
else
BOOST_CHECK(cborMetadata.at("solc") == VersionStringStrict);
BOOST_CHECK(cborMetadata.count("bzzr0") == 1);
BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash));
BOOST_CHECK(cborMetadata.count("experimental") == 1);
BOOST_CHECK(cborMetadata.at("experimental") == "true");
for(auto release: std::set<bool>{true, VersionIsRelease})
{
CompilerStack compilerStack;
compilerStack.overwriteReleaseFlag(release);
compilerStack.setSources({{"", std::string(sourceCode)}});
compilerStack.setEVMVersion(dev::test::Options::get().evmVersion());
compilerStack.setOptimiserSettings(dev::test::Options::get().optimize);
BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed");
bytes const& bytecode = compilerStack.runtimeObject("test").bytecode;
std::string const& metadata = compilerStack.metadata("test");
BOOST_CHECK(dev::test::isValidMetadata(metadata));
bytes hash = dev::swarmHash(metadata).asBytes();
BOOST_REQUIRE(hash.size() == 32);
auto const cborMetadata = requireParsedCBORMetadata(bytecode);
BOOST_CHECK(cborMetadata.size() == 3);
BOOST_CHECK(cborMetadata.count("solc") == 1);
if (release)
BOOST_CHECK(cborMetadata.at("solc") == toHex(VersionCompactBytes));
else
BOOST_CHECK(cborMetadata.at("solc") == VersionStringStrict);
BOOST_CHECK(cborMetadata.count("bzzr0") == 1);
BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash));
BOOST_CHECK(cborMetadata.count("experimental") == 1);
BOOST_CHECK(cborMetadata.at("experimental") == "true");
}
}
BOOST_AUTO_TEST_CASE(metadata_relevant_sources)

View File

@ -22,6 +22,7 @@
#include <string>
#include <boost/test/unit_test.hpp>
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/Version.h>
#include <libdevcore/JSON.h>
#include <test/Metadata.h>
@ -326,7 +327,9 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString());
BOOST_CHECK_EQUAL(
dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()),
"6080604052348015600f57600080fd5b50603580601d6000396000f3fe6080604052600080fdfe"
string("6080604052348015600f57600080fd5b5060") +
(VersionIsRelease ? "3e" : toHex(bytes{uint8_t(60 + VersionStringStrict.size())})) +
"80601d6000396000f3fe6080604052600080fdfe"
);
BOOST_CHECK(contract["evm"]["assembly"].isString());
BOOST_CHECK(contract["evm"]["assembly"].asString().find(
@ -338,12 +341,19 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
"tag_1:\n /* \"fileA\":0:14 contract A { } */\n pop\n dataSize(sub_0)\n dup1\n "
"dataOffset(sub_0)\n 0x00\n codecopy\n 0x00\n return\nstop\n\nsub_0: assembly {\n "
"/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n 0x00\n "
"dup1\n revert\n\n auxdata: 0xa165627a7a72305820"
"dup1\n revert\n\n auxdata: 0xa265627a7a72305820"
) == 0);
BOOST_CHECK(contract["evm"]["gasEstimates"].isObject());
BOOST_CHECK_EQUAL(contract["evm"]["gasEstimates"].size(), 1);
BOOST_CHECK(contract["evm"]["gasEstimates"]["creation"].isObject());
BOOST_CHECK_EQUAL(contract["evm"]["gasEstimates"]["creation"].size(), 3);
BOOST_CHECK(contract["evm"]["gasEstimates"]["creation"]["codeDepositCost"].isString());
BOOST_CHECK(contract["evm"]["gasEstimates"]["creation"]["executionCost"].isString());
BOOST_CHECK(contract["evm"]["gasEstimates"]["creation"]["totalCost"].isString());
BOOST_CHECK_EQUAL(
dev::jsonCompactPrint(contract["evm"]["gasEstimates"]),
"{\"creation\":{\"codeDepositCost\":\"10600\",\"executionCost\":\"66\",\"totalCost\":\"10666\"}}"
u256(contract["evm"]["gasEstimates"]["creation"]["codeDepositCost"].asString()) +
u256(contract["evm"]["gasEstimates"]["creation"]["executionCost"].asString()),
u256(contract["evm"]["gasEstimates"]["creation"]["totalCost"].asString())
);
// Lets take the top level `.code` section (the "deployer code"), that should expose most of the features of
// the assembly JSON. What we want to check here is Operation, Push, PushTag, PushSub, PushSubSize and Tag.

View File

@ -13,7 +13,10 @@ contract C {
function f8(uint[32] memory, string[] memory, uint32, address) public returns (uint[] memory, uint16[] memory) {}
}
// ----
// creation: 1160 + 1119000 = 1120160
// creation:
// codeDepositCost: 1120800
// executionCost: 1167
// totalCost: 1121967
// external:
// a(): 530
// b(uint256): infinite

View File

@ -16,7 +16,10 @@ contract C {
// optimize: true
// optimize-yul: true
// ----
// creation: 645 + 608800 = 609445
// creation:
// codeDepositCost: 610600
// executionCost: 645
// totalCost: 611245
// external:
// a(): 429
// b(uint256): 884

View File

@ -12,6 +12,9 @@ contract C {
}
}
// ----
// creation: 294 + 255000 = 255294
// creation:
// codeDepositCost: 256800
// executionCost: 300
// totalCost: 257100
// external:
// f(): 252

View File

@ -23,7 +23,10 @@ contract Large {
function g0(uint x) public payable returns (uint) { require(x > 10); }
}
// ----
// creation: 670 + 635000 = 635670
// creation:
// codeDepositCost: 636800
// executionCost: 670
// totalCost: 637470
// external:
// a(): 451
// b(uint256): 846

View File

@ -26,7 +26,10 @@ contract Large {
// optimize: true
// optimize-runs: 2
// ----
// creation: 300 + 260000 = 260300
// creation:
// codeDepositCost: 261800
// executionCost: 300
// totalCost: 262100
// external:
// a(): 398
// b(uint256): 1105

View File

@ -10,7 +10,10 @@ contract Medium {
function g0(uint x) public payable returns (uint) { require(x > 10); }
}
// ----
// creation: 294 + 251200 = 251494
// creation:
// codeDepositCost: 253000
// executionCost: 294
// totalCost: 253294
// external:
// a(): 428
// b(uint256): 846

View File

@ -13,7 +13,10 @@ contract Medium {
// optimize: true
// optimize-runs: 2
// ----
// creation: 183 + 140400 = 140583
// creation:
// codeDepositCost: 142200
// executionCost: 190
// totalCost: 142390
// external:
// a(): 398
// b(uint256): 863

View File

@ -5,7 +5,10 @@ contract Small {
function () external payable {}
}
// ----
// creation: 129 + 81800 = 81929
// creation:
// codeDepositCost: 83600
// executionCost: 135
// totalCost: 83735
// external:
// fallback: 118
// a(): 383

View File

@ -8,7 +8,10 @@ contract Small {
// optimize: true
// optimize-runs: 2
// ----
// creation: 111 + 63600 = 63711
// creation:
// codeDepositCost: 65400
// executionCost: 117
// totalCost: 65517
// external:
// fallback: 118
// a(): 376