Skip to content

Commit 9623386

Browse files
committed
Separate 'code' gas settings in semantic tests
1 parent 2ee4d6b commit 9623386

File tree

7 files changed

+231
-24
lines changed

7 files changed

+231
-24
lines changed

test/libsolidity/SemanticTest.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,11 @@ bool SemanticTest::checkGasCostExpectation(TestFunctionCall& io_test, bool _comp
569569
(_compileViaYul ? "ir"s : "legacy"s) +
570570
(m_optimiserSettings == OptimiserSettings::full() ? "Optimized" : "");
571571

572+
soltestAssert(
573+
io_test.call().expectations.gasUsed.count(setting) ==
574+
io_test.call().expectations.gasUsedForCodeDeposit.count(setting)
575+
);
576+
572577
// We don't check gas if enforce gas cost is not active
573578
// or test is run with abi encoder v1 only
574579
// or gas used less than threshold for enforcing feature
@@ -587,9 +592,12 @@ bool SemanticTest::checkGasCostExpectation(TestFunctionCall& io_test, bool _comp
587592
solAssert(!m_runWithABIEncoderV1Only, "");
588593

589594
io_test.setGasCost(setting, m_gasUsed);
595+
io_test.setCodeDepositGasCost(setting, m_gasUsedForCodeDeposit);
596+
590597
return
591598
io_test.call().expectations.gasUsed.count(setting) > 0 &&
592-
m_gasUsed == io_test.call().expectations.gasUsed.at(setting);
599+
m_gasUsed == io_test.call().expectations.gasUsed.at(setting) &&
600+
m_gasUsedForCodeDeposit == io_test.call().expectations.gasUsedForCodeDeposit.at(setting);
593601
}
594602

595603
void SemanticTest::printSource(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) const

test/libsolidity/util/SoltestTypes.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ struct FunctionCallExpectations
220220
/// bytecode (and therefore the cost), except for EVM version. E.g. IR codegen without
221221
/// optimization legacy codegen with optimization.
222222
std::map<std::string, u256> gasUsed;
223+
224+
/// The portion of @a gasUsed spent on code deposits of newly created contracts.
225+
/// May exceed @a gasUsed in rare corner cases due to refunds.
226+
/// Keys must always match @a gasUsedExcludingCode.
227+
std::map<std::string, u256> gasUsedForCodeDeposit;
223228
};
224229

225230
/**

test/libsolidity/util/TestFileParser.cpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,31 @@ std::vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctio
8484
BOOST_THROW_EXCEPTION(TestParserError("Expected function call before gas usage filter."));
8585

8686
std::string runType = m_scanner.currentLiteral();
87-
if (std::set<std::string>{"ir", "irOptimized", "legacy", "legacyOptimized"}.count(runType) > 0)
88-
{
89-
m_scanner.scanNextToken();
90-
expect(Token::Colon);
91-
if (calls.back().expectations.gasUsed.count(runType) > 0)
92-
throw TestParserError("Gas usage expectation set multiple times.");
93-
calls.back().expectations.gasUsed[runType] = u256(parseDecimalNumber());
94-
}
95-
else
87+
if (std::set<std::string>{"ir", "irOptimized", "legacy", "legacyOptimized"}.count(runType) == 0)
9688
BOOST_THROW_EXCEPTION(TestParserError(
9789
"Expected \"ir\", \"irOptimized\", \"legacy\", or \"legacyOptimized\"."
9890
));
91+
m_scanner.scanNextToken();
92+
93+
bool isCodeDepositCost = false;
94+
if (accept(Token::Identifier))
95+
{
96+
if (m_scanner.currentLiteral() != "code")
97+
BOOST_THROW_EXCEPTION(TestParserError("Expected \"code\" or \":\"."));
98+
isCodeDepositCost = true;
99+
m_scanner.scanNextToken();
100+
}
101+
102+
expect(Token::Colon);
103+
104+
std::map<std::string, u256>& gasExpectationMap = (isCodeDepositCost ?
105+
calls.back().expectations.gasUsedForCodeDeposit :
106+
calls.back().expectations.gasUsed
107+
);
108+
if (gasExpectationMap.count(runType) > 0)
109+
throw TestParserError("Gas usage expectation set multiple times.");
110+
111+
gasExpectationMap[runType] = u256(parseDecimalNumber());
99112
}
100113
else
101114
{
@@ -189,6 +202,17 @@ std::vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctio
189202
}
190203
}
191204
}
205+
206+
for (FunctionCall& call: calls)
207+
{
208+
// Ensure that each specified gas expectation has both components to simplify working with them.
209+
for (auto const& [runType, gas]: call.expectations.gasUsedForCodeDeposit)
210+
call.expectations.gasUsed.try_emplace({runType, 0});
211+
212+
for (auto const& [runType, gas]: call.expectations.gasUsed)
213+
call.expectations.gasUsedForCodeDeposit.try_emplace({runType, 0});
214+
}
215+
192216
return calls;
193217
}
194218

test/libsolidity/util/TestFileParserTests.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,11 @@ BOOST_AUTO_TEST_CASE(gas)
10791079
{"legacy", 5000},
10801080
{"legacyOptimized", 0},
10811081
}));
1082+
BOOST_TEST(calls[0].expectations.gasUsedForCodeDeposit == (std::map<std::string, u256>{
1083+
{"ir", 0},
1084+
{"legacy", 0},
1085+
{"legacyOptimized", 0},
1086+
}));
10821087
}
10831088

10841089
BOOST_AUTO_TEST_CASE(gas_before_call)
@@ -1109,6 +1114,103 @@ BOOST_AUTO_TEST_CASE(gas_duplicate_run_type)
11091114
)";
11101115
BOOST_REQUIRE_THROW(parse(source), TestParserError);
11111116
}
1117+
1118+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost)
1119+
{
1120+
char const* source = R"(
1121+
// f() ->
1122+
// gas legacyOptimized code: 1
1123+
// gas ir: 13000
1124+
// gas irOptimized: 6666
1125+
// gas irOptimized code: 666
1126+
// gas legacy code: 0
1127+
// gas legacyOptimized: 2
1128+
)";
1129+
auto const calls = parse(source);
1130+
BOOST_REQUIRE_EQUAL(calls.size(), 1);
1131+
BOOST_REQUIRE_EQUAL(calls[0].expectations.failure, false);
1132+
BOOST_TEST(calls[0].expectations.gasUsed == (std::map<std::string, u256>{
1133+
{"ir", 13000},
1134+
{"irOptimized", 6666},
1135+
{"legacy", 0},
1136+
{"legacyOptimized", 2},
1137+
}));
1138+
BOOST_TEST(calls[0].expectations.gasUsedForCodeDeposit == (std::map<std::string, u256>{
1139+
{"ir", 0},
1140+
{"irOptimized", 666},
1141+
{"legacy", 0},
1142+
{"legacyOptimized", 1},
1143+
}));
1144+
}
1145+
1146+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost_invalid_suffix)
1147+
{
1148+
char const* source = R"(
1149+
// f() ->
1150+
// gas ir data: 3245
1151+
)";
1152+
BOOST_REQUIRE_THROW(parse(source), TestParserError);
1153+
}
1154+
1155+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost_tokens_after_suffix)
1156+
{
1157+
char const* source = R"(
1158+
// f() ->
1159+
// gas ir code code: 3245
1160+
)";
1161+
BOOST_REQUIRE_THROW(parse(source), TestParserError);
1162+
}
1163+
1164+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost_double_code_gas)
1165+
{
1166+
char const* source = R"(
1167+
// f() ->
1168+
// gas ir: 3245
1169+
// gas ir code: 1
1170+
// gas ir code: 1
1171+
)";
1172+
BOOST_REQUIRE_THROW(parse(source), TestParserError);
1173+
}
1174+
1175+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost_negative_non_code_cost)
1176+
{
1177+
// NOTE: This arrangement is unlikely but may still be possible due to refunds.
1178+
char const* source = R"(
1179+
// f() ->
1180+
// gas ir: 10
1181+
// gas ir code: 20
1182+
)";
1183+
auto const calls = parse(source);
1184+
BOOST_REQUIRE_EQUAL(calls.size(), 1);
1185+
BOOST_REQUIRE_EQUAL(calls[0].expectations.failure, false);
1186+
BOOST_TEST(calls[0].expectations.gasUsed == (std::map<std::string, u256>{
1187+
{"ir", 10},
1188+
}));
1189+
BOOST_TEST(calls[0].expectations.gasUsedForCodeDeposit == (std::map<std::string, u256>{
1190+
{"ir", 20},
1191+
}));
1192+
}
1193+
1194+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost_negative_total_cost)
1195+
{
1196+
char const* source = R"(
1197+
// f() ->
1198+
// gas ir: -10
1199+
// gas ir code: 20
1200+
)";
1201+
BOOST_REQUIRE_THROW(parse(source), TestParserError);
1202+
}
1203+
1204+
BOOST_AUTO_TEST_CASE(gas_with_code_deposit_cost_negative_code_cost)
1205+
{
1206+
char const* source = R"(
1207+
// f() ->
1208+
// gas ir: 20
1209+
// gas ir code: -10
1210+
)";
1211+
BOOST_REQUIRE_THROW(parse(source), TestParserError);
1212+
}
1213+
11121214
BOOST_AUTO_TEST_SUITE_END()
11131215

11141216
}

test/libsolidity/util/TestFunctionCall.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include <boost/algorithm/string.hpp>
2323

2424
#include <fmt/format.h>
25+
#include <range/v3/view/map.hpp>
26+
#include <range/v3/view/set_algorithm.hpp>
27+
2528
#include <optional>
2629
#include <stdexcept>
2730
#include <string>
@@ -371,18 +374,37 @@ std::string TestFunctionCall::formatGasExpectations(
371374
bool _showDifference
372375
) const
373376
{
377+
using ranges::views::keys;
378+
using ranges::views::set_symmetric_difference;
379+
380+
soltestAssert(set_symmetric_difference(m_codeDepositGasCosts | keys, m_gasCosts | keys).empty());
381+
soltestAssert(set_symmetric_difference(m_call.expectations.gasUsedForCodeDeposit | keys, m_call.expectations.gasUsed | keys).empty());
382+
374383
std::stringstream os;
375384
for (auto const& [runType, gasUsed]: (_useActualCost ? m_gasCosts : m_call.expectations.gasUsed))
376385
{
377386
soltestAssert(runType != "");
378387

388+
u256 gasUsedForCodeDeposit = (_useActualCost ? m_codeDepositGasCosts : m_call.expectations.gasUsedForCodeDeposit).at(runType);
389+
379390
os << std::endl << _linePrefix << "// gas " << runType << ": " << gasUsed.str();
380391
std::string gasDiff = formatGasDiff(
381392
gasOrNullopt(m_gasCosts, runType),
382393
gasOrNullopt(m_call.expectations.gasUsed, runType)
383394
);
384395
if (_showDifference && !gasDiff.empty() && _useActualCost)
385396
os << " [" << gasDiff << "]";
397+
398+
if (gasUsedForCodeDeposit != 0)
399+
{
400+
os << std::endl << _linePrefix << "// gas " << runType << " code: " << gasUsedForCodeDeposit.str();
401+
std::string codeGasDiff = formatGasDiff(
402+
gasOrNullopt(m_codeDepositGasCosts, runType),
403+
gasOrNullopt(m_call.expectations.gasUsedForCodeDeposit, runType)
404+
);
405+
if (_showDifference && !codeGasDiff.empty() && _useActualCost)
406+
os << " [" << codeGasDiff << "]";
407+
}
386408
}
387409
return os.str();
388410
}

test/libsolidity/util/TestFunctionCall.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ class TestFunctionCall
5151
ExpectedValuesActualGas
5252
};
5353

54-
TestFunctionCall(FunctionCall _call): m_call(std::move(_call)), m_gasCosts(m_call.expectations.gasUsed) {}
54+
TestFunctionCall(FunctionCall _call):
55+
m_call(std::move(_call)),
56+
m_gasCosts(m_call.expectations.gasUsed),
57+
m_codeDepositGasCosts(m_call.expectations.gasUsedForCodeDeposit)
58+
{}
5559

5660
/// Formats this function call test and applies the format that was detected during parsing.
5761
/// _renderMode determines the source of values to be inserted into the updated test expectations.
@@ -94,6 +98,7 @@ class TestFunctionCall
9498
void setFailure(const bool _failure) { m_failure = _failure; }
9599
void setRawBytes(const bytes _rawBytes) { m_rawBytes = _rawBytes; }
96100
void setGasCost(std::string const& _runType, u256 const& _gasCost) { m_gasCosts[_runType] = _gasCost; }
101+
void setCodeDepositGasCost(std::string const& _runType, u256 const& _gasCost) { m_codeDepositGasCosts[_runType] = _gasCost; }
97102
void setContractABI(Json::Value _contractABI) { m_contractABI = std::move(_contractABI); }
98103
void setSideEffects(std::vector<std::string> _sideEffects) { m_call.actualSideEffects = _sideEffects; }
99104

@@ -143,6 +148,8 @@ class TestFunctionCall
143148
bytes m_rawBytes = bytes{};
144149
/// Actual gas costs
145150
std::map<std::string, u256> m_gasCosts;
151+
/// Actual code deposit gas costs
152+
std::map<std::string, u256> m_codeDepositGasCosts;
146153
/// Transaction status of the actual call. False in case of a REVERT or any other failure.
147154
bool m_failure = true;
148155
/// JSON object which holds the contract ABI and that is used to set the output formatting

0 commit comments

Comments
 (0)