Merge pull request #6036 from ethereum/soltest-hexliterals

[soltest] Add support for left-aligned hex literals
This commit is contained in:
chriseth 2019-02-21 19:11:27 +01:00 committed by GitHub
commit 773a3ff880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 16 deletions

View File

@ -22,7 +22,7 @@ Bugfixes:
Build System: Build System:
* Soltest: Add support for left-aligned, padded hex literals.
### 0.5.4 (2019-02-12) ### 0.5.4 (2019-02-12)

View File

@ -8,6 +8,9 @@ contract C {
function h() public payable returns (uint) { function h() public payable returns (uint) {
return f(); return f();
} }
function x(bytes32 b) public returns (bytes32) {
return b;
}
} }
// ---- // ----
// f() -> 1 // f() -> 1
@ -15,3 +18,4 @@ contract C {
// h(), 1 ether -> 1 // h(), 1 ether -> 1
// j() -> FAILURE // j() -> FAILURE
// i() # Does not exist. # -> FAILURE # Reverts. # // i() # Does not exist. # -> FAILURE # Reverts. #
// x(bytes32): 0x31 -> 0x31

View File

@ -137,7 +137,7 @@ string TestFileParser::parseFunctionSignature()
u256 TestFileParser::parseFunctionCallValue() u256 TestFileParser::parseFunctionCallValue()
{ {
u256 value = convertNumber(parseNumber()); u256 value = convertNumber(parseDecimalNumber());
expect(Token::Ether); expect(Token::Ether);
return value; return value;
} }
@ -203,16 +203,23 @@ tuple<bytes, ABIType, string> TestFileParser::parseABITypeLiteral()
abiType = ABIType{ABIType::SignedDec, ABIType::AlignRight, 32}; abiType = ABIType{ABIType::SignedDec, ABIType::AlignRight, 32};
expect(Token::Sub); expect(Token::Sub);
rawString += formatToken(Token::Sub); rawString += formatToken(Token::Sub);
string parsed = parseNumber(); string parsed = parseDecimalNumber();
rawString += parsed; rawString += parsed;
number = convertNumber(parsed) * -1; number = convertNumber(parsed) * -1;
} }
else else
{ {
if (accept(Token::Number)) if (accept(Token::HexNumber))
{
abiType = ABIType{ABIType::Hex, ABIType::AlignLeft, 32};
string parsed = parseHexNumber();
rawString += parsed;
return make_tuple(convertHexNumber(parsed), abiType, rawString);
}
else if (accept(Token::Number))
{ {
abiType = ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32}; abiType = ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32};
string parsed = parseNumber(); string parsed = parseDecimalNumber();
rawString += parsed; rawString += parsed;
number = convertNumber(parsed); number = convertNumber(parsed);
} }
@ -263,16 +270,24 @@ string TestFileParser::parseComment()
return string{}; return string{};
} }
string TestFileParser::parseNumber() string TestFileParser::parseDecimalNumber()
{ {
string literal = m_scanner.currentLiteral(); string literal = m_scanner.currentLiteral();
expect(Token::Number); expect(Token::Number);
return literal; return literal;
} }
string TestFileParser::parseHexNumber()
{
string literal = m_scanner.currentLiteral();
expect(Token::HexNumber);
return literal;
}
u256 TestFileParser::convertNumber(string const& _literal) u256 TestFileParser::convertNumber(string const& _literal)
{ {
try { try
{
return u256{_literal}; return u256{_literal};
} }
catch (std::exception const&) catch (std::exception const&)
@ -281,6 +296,26 @@ u256 TestFileParser::convertNumber(string const& _literal)
} }
} }
bytes TestFileParser::convertHexNumber(string const& _literal)
{
try
{
if (_literal.size() % 2)
{
throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
}
else
{
bytes result = fromHex(_literal);
return result + bytes(32 - result.size(), 0);
}
}
catch (std::exception const&)
{
throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
}
}
void TestFileParser::Scanner::readStream(istream& _stream) void TestFileParser::Scanner::readStream(istream& _stream)
{ {
std::string line; std::string line;
@ -348,7 +383,16 @@ void TestFileParser::Scanner::scanNextToken()
token = selectToken(detectedToken.first, detectedToken.second); token = selectToken(detectedToken.first, detectedToken.second);
} }
else if (langutil::isDecimalDigit(current())) else if (langutil::isDecimalDigit(current()))
token = selectToken(Token::Number, scanNumber()); {
if (current() == '0' && peek() == 'x')
{
advance();
advance();
token = selectToken(Token::HexNumber, "0x" + scanHexNumber());
}
else
token = selectToken(Token::Number, scanDecimalNumber());
}
else if (langutil::isWhiteSpace(current())) else if (langutil::isWhiteSpace(current()))
token = selectToken(Token::Whitespace); token = selectToken(Token::Whitespace);
else if (isEndOfLine()) else if (isEndOfLine())
@ -385,7 +429,7 @@ string TestFileParser::Scanner::scanIdentifierOrKeyword()
return identifier; return identifier;
} }
string TestFileParser::Scanner::scanNumber() string TestFileParser::Scanner::scanDecimalNumber()
{ {
string number; string number;
number += current(); number += current();
@ -396,3 +440,15 @@ string TestFileParser::Scanner::scanNumber()
} }
return number; return number;
} }
string TestFileParser::Scanner::scanHexNumber()
{
string number;
number += current();
while (langutil::isHexDigit(peek()))
{
advance();
number += current();
}
return number;
}

View File

@ -56,6 +56,7 @@ namespace test
/* Literals & identifier */ \ /* Literals & identifier */ \
T(Comment, "#", 0) \ T(Comment, "#", 0) \
T(Number, "number", 0) \ T(Number, "number", 0) \
T(HexNumber, "hex_number", 0) \
T(Identifier, "identifier", 0) \ T(Identifier, "identifier", 0) \
/* type keywords */ \ /* type keywords */ \
K(Ether, "ether", 0) \ K(Ether, "ether", 0) \
@ -102,10 +103,10 @@ struct ABIType
{ {
UnsignedDec, UnsignedDec,
SignedDec, SignedDec,
Hex,
Failure, Failure,
None None
}; };
enum Align enum Align
{ {
AlignLeft, AlignLeft,
@ -113,7 +114,7 @@ struct ABIType
}; };
Type type = ABIType::None; Type type = ABIType::None;
Align align = Align::AlignRight; Align align = ABIType::AlignRight;
size_t size = 0; size_t size = 0;
}; };
@ -290,7 +291,8 @@ private:
std::string scanComment(); std::string scanComment();
std::string scanIdentifierOrKeyword(); std::string scanIdentifierOrKeyword();
std::string scanNumber(); std::string scanDecimalNumber();
std::string scanHexNumber();
private: private:
using TokenDesc = std::pair<Token, std::string>; using TokenDesc = std::pair<Token, std::string>;
@ -357,13 +359,21 @@ private:
/// # A nice comment. # /// # A nice comment. #
std::string parseComment(); std::string parseComment();
/// Parses the current number literal. /// Parses the current decimal number literal.
std::string parseNumber(); std::string parseDecimalNumber();
/// Tries to convert \param _literal to `uint256` and throws if /// Parses the current hex number literal.
/// conversion fails. std::string parseHexNumber();
/// Tries to convert \param _literal to right-aligned, padded `u256`
/// representation of the decimal number literal.
/// Throws if conversion fails.
u256 convertNumber(std::string const& _literal); u256 convertNumber(std::string const& _literal);
/// Tries to convert \param _literal to left-aligned, padded `bytes`
/// representation of the hex literal. Throws if conversion fails.
bytes convertHexNumber(std::string const& _literal);
/// A scanner instance /// A scanner instance
Scanner m_scanner; Scanner m_scanner;
}; };

View File

@ -305,6 +305,38 @@ BOOST_AUTO_TEST_CASE(call_arguments_tuple)
testFunctionCall(calls.at(1), Mode::SingleLine, "f((uint8),uint8)", false); testFunctionCall(calls.at(1), Mode::SingleLine, "f((uint8),uint8)", false);
} }
BOOST_AUTO_TEST_CASE(call_arguments_left_aligned)
{
char const* source = R"(
// f(bytes32, bytes32): 0x6161, 0x420000EF -> 1
// g(bytes32, bytes32): 0x0616, 0x0042EF00 -> 1
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 2);
testFunctionCall(
calls.at(0),
Mode::SingleLine,
"f(bytes32,bytes32)",
false,
fmt::encodeArgs(
u256("0x6161000000000000000000000000000000000000000000000000000000000000"),
u256("0x420000EF00000000000000000000000000000000000000000000000000000000")
),
fmt::encodeArgs(1)
);
testFunctionCall(
calls.at(1),
Mode::SingleLine,
"g(bytes32,bytes32)",
false,
fmt::encodeArgs(
u256("0x0616000000000000000000000000000000000000000000000000000000000000"),
u256("0x0042EF0000000000000000000000000000000000000000000000000000000000")
),
fmt::encodeArgs(1)
);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple_of_tuples) BOOST_AUTO_TEST_CASE(call_arguments_tuple_of_tuples)
{ {
char const* source = R"( char const* source = R"(
@ -546,6 +578,14 @@ BOOST_AUTO_TEST_CASE(call_ether_type_invalid)
BOOST_REQUIRE_THROW(parse(source), langutil::Error); BOOST_REQUIRE_THROW(parse(source), langutil::Error);
} }
BOOST_AUTO_TEST_CASE(call_hex_number_invalid)
{
char const* source = R"(
// f(bytes32, bytes32): 0x616, 0x042 -> 1
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_arguments_colon) BOOST_AUTO_TEST_CASE(call_arguments_colon)
{ {
char const* source = R"( char const* source = R"(

View File

@ -144,6 +144,13 @@ string TestFunctionCall::formatBytesParameters(bytes const& _bytes, ParameterLis
else else
resultStream << fromBigEndian<u256>(byteRange); resultStream << fromBigEndian<u256>(byteRange);
break; break;
case ABIType::Hex:
soltestAssert(param.abiType.align == ABIType::AlignLeft, "Hex numbers must be left-aligned.");
byteRange.erase(
std::remove(byteRange.begin(), byteRange.end(), 0), byteRange.end()
);
resultStream << toHex(byteRange, HexPrefix::Add);
break;
case ABIType::Failure: case ABIType::Failure:
break; break;
case ABIType::None: case ABIType::None:

View File

@ -75,6 +75,34 @@ BOOST_AUTO_TEST_CASE(format_signed_singleline)
BOOST_REQUIRE_EQUAL(test.format(), "// f(int8): -1 -> -1"); BOOST_REQUIRE_EQUAL(test.format(), "// f(int8): -1 -> -1");
} }
BOOST_AUTO_TEST_CASE(format_hex_singleline)
{
bytes result = fromHex("0x31");
bytes expectedBytes = result + bytes(32 - result.size(), 0);
ABIType abiType{ABIType::Hex, ABIType::AlignLeft, 32};
Parameter param{expectedBytes, "0x31", abiType, FormatInfo{}};
FunctionCallExpectations expectations{vector<Parameter>{param}, false, string{}};
FunctionCallArgs arguments{vector<Parameter>{param}, string{}};
FunctionCall call{"f(bytes32)", 0, arguments, expectations};
TestFunctionCall test{call};
BOOST_REQUIRE_EQUAL(test.format(), "// f(bytes32): 0x31 -> 0x31");
}
BOOST_AUTO_TEST_CASE(format_hex_right_align)
{
bytes result = fromHex("0x31");
bytes expectedBytes = result + bytes(32 - result.size(), 0);
ABIType abiType{ABIType::Hex, ABIType::AlignRight, 32};
Parameter param{expectedBytes, "0x31", abiType, FormatInfo{}};
FunctionCallExpectations expectations{vector<Parameter>{param}, false, string{}};
FunctionCallArgs arguments{vector<Parameter>{param}, string{}};
FunctionCall call{"f(bytes32)", 0, arguments, expectations};
TestFunctionCall test{call};
BOOST_REQUIRE_THROW(test.format(), runtime_error);
}
BOOST_AUTO_TEST_CASE(format_empty_byte_range) BOOST_AUTO_TEST_CASE(format_empty_byte_range)
{ {
bytes expectedBytes; bytes expectedBytes;