Adds support for string literals to soltest.

This commit is contained in:
Erik Kundt 2019-05-06 10:08:10 +02:00
parent befadea0c6
commit 9956319e8b
5 changed files with 118 additions and 19 deletions

View File

@ -20,6 +20,15 @@ contract C {
function m(bytes memory b) public returns (bytes memory) { function m(bytes memory b) public returns (bytes memory) {
return b; return b;
} }
function n() public returns (string memory) {
return "any";
}
function o() public returns (string memory, string memory) {
return ("any", "any");
}
function p() public returns (string memory, uint, string memory) {
return ("any", 42, "any");
}
} }
// ---- // ----
// _() -> FAILURE // _() -> FAILURE
@ -33,3 +42,6 @@ contract C {
// m(bytes): 32, 32, 0x20 -> 32, 32, 0x20 // m(bytes): 32, 32, 0x20 -> 32, 32, 0x20
// m(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB) // m(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB)
// m(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000" // m(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000"
// n() -> 0x20, 3, "any"
// o() -> 0x40, 0x80, 3, "any", 3, "any"
// p() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any"

View File

@ -290,11 +290,22 @@ tuple<bytes, ABIType, string> TestFileParser::parseABITypeLiteral()
throw Error(Error::Type::ParserError, "Invalid hex string literal."); throw Error(Error::Type::ParserError, "Invalid hex string literal.");
if (alignment != DeclaredAlignment::None) if (alignment != DeclaredAlignment::None)
throw Error(Error::Type::ParserError, "Hex string literals cannot be aligned or padded."); throw Error(Error::Type::ParserError, "Hex string literals cannot be aligned or padded.");
string parsed = parseHexNumber(); string parsed = parseString();
rawString += "hex\"" + parsed + "\""; rawString += "hex\"" + parsed + "\"";
result = convertHexString(parsed); result = convertHexNumber(parsed);
abiType = ABIType{ABIType::HexString, ABIType::AlignNone, result.size()}; abiType = ABIType{ABIType::HexString, ABIType::AlignNone, result.size()};
} }
else if (accept(Token::String))
{
if (isSigned)
throw Error(Error::Type::ParserError, "Invalid string literal.");
if (alignment != DeclaredAlignment::None)
throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded.");
abiType = ABIType{ABIType::String, ABIType::AlignLeft, 32};
string parsed = parseString();
rawString += "\"" + parsed + "\"";
result = applyAlign(DeclaredAlignment::Left, abiType, convertString(parsed));
}
else if (accept(Token::Number)) else if (accept(Token::Number))
{ {
auto type = isSigned ? ABIType::SignedDec : ABIType::UnsignedDec; auto type = isSigned ? ABIType::SignedDec : ABIType::UnsignedDec;
@ -395,6 +406,13 @@ string TestFileParser::parseHexNumber()
return literal; return literal;
} }
string TestFileParser::parseString()
{
string literal = m_scanner.currentLiteral();
expect(Token::String);
return literal;
}
bytes TestFileParser::convertBoolean(string const& _literal) bytes TestFileParser::convertBoolean(string const& _literal)
{ {
if (_literal == "true") if (_literal == "true")
@ -422,13 +440,9 @@ bytes TestFileParser::convertHexNumber(string const& _literal)
try try
{ {
if (_literal.size() % 2) if (_literal.size() % 2)
{
throw Error(Error::Type::ParserError, "Hex number encoding invalid."); throw Error(Error::Type::ParserError, "Hex number encoding invalid.");
}
else else
{
return fromHex(_literal); return fromHex(_literal);
}
} }
catch (std::exception const&) catch (std::exception const&)
{ {
@ -436,18 +450,15 @@ bytes TestFileParser::convertHexNumber(string const& _literal)
} }
} }
bytes TestFileParser::convertHexString(string const& _literal) bytes TestFileParser::convertString(string const& _literal)
{ {
try try
{ {
if (_literal.size() % 2) return asBytes(_literal);
throw Error(Error::Type::ParserError, "Hex string encoding invalid.");
else
return fromHex(_literal);
} }
catch (std::exception const&) catch (std::exception const&)
{ {
throw Error(Error::Type::ParserError, "Hex string encoding invalid."); throw Error(Error::Type::ParserError, "String encoding invalid.");
} }
} }
@ -525,9 +536,7 @@ void TestFileParser::Scanner::scanNextToken()
token = selectToken(Token::RBrack); token = selectToken(Token::RBrack);
break; break;
case '\"': case '\"':
advance(); token = selectToken(Token::String, scanString());
token = selectToken(Token::HexNumber, scanHexNumber());
advance();
break; break;
default: default:
if (isIdentifierStart(current())) if (isIdentifierStart(current()))
@ -610,3 +619,16 @@ string TestFileParser::Scanner::scanHexNumber()
} }
return number; return number;
} }
string TestFileParser::Scanner::scanString()
{
string str;
advance();
while (current() != '\"')
{
str += current();
advance();
}
return str;
}

View File

@ -57,6 +57,7 @@ namespace test
T(Comment, "#", 0) \ T(Comment, "#", 0) \
T(Number, "number", 0) \ T(Number, "number", 0) \
T(HexNumber, "hex_number", 0) \ T(HexNumber, "hex_number", 0) \
T(String, "string", 0) \
T(Identifier, "identifier", 0) \ T(Identifier, "identifier", 0) \
/* type keywords */ \ /* type keywords */ \
K(Ether, "ether", 0) \ K(Ether, "ether", 0) \
@ -111,7 +112,8 @@ struct ABIType
UnsignedDec, UnsignedDec,
SignedDec, SignedDec,
Hex, Hex,
HexString HexString,
String
}; };
enum Align enum Align
{ {
@ -300,6 +302,7 @@ private:
std::string scanIdentifierOrKeyword(); std::string scanIdentifierOrKeyword();
std::string scanDecimalNumber(); std::string scanDecimalNumber();
std::string scanHexNumber(); std::string scanHexNumber();
std::string scanString();
private: private:
using TokenDesc = std::pair<Token, std::string>; using TokenDesc = std::pair<Token, std::string>;
@ -375,6 +378,9 @@ private:
/// Parses the current hex number literal. /// Parses the current hex number literal.
std::string parseHexNumber(); std::string parseHexNumber();
/// Parses the current string literal.
std::string parseString();
/// Tries to convert \param _literal to an unpadded `bytes` /// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the boolean number literal. Throws if conversion fails. /// representation of the boolean number literal. Throws if conversion fails.
bytes convertBoolean(std::string const& _literal); bytes convertBoolean(std::string const& _literal);
@ -387,9 +393,9 @@ private:
/// representation of the hex literal. Throws if conversion fails. /// representation of the hex literal. Throws if conversion fails.
bytes convertHexNumber(std::string const& _literal); bytes convertHexNumber(std::string const& _literal);
/// Tries to convert \param _literal to left-aligned, unpadded `bytes` /// Tries to convert \param _literal to an unpadded `bytes`
/// representation of the hex string literal. Throws if conversion fails. /// representation of the string literal. Throws if conversion fails.
bytes convertHexString(std::string const& _literal); bytes convertString(std::string const& _literal);
/// A scanner instance /// A scanner instance
Scanner m_scanner; Scanner m_scanner;

View File

@ -344,6 +344,39 @@ BOOST_AUTO_TEST_CASE(call_arguments_hex_string_lowercase)
); );
} }
BOOST_AUTO_TEST_CASE(call_arguments_string)
{
char const* source = R"(
// f(string): 0x20, 3, "any" ->
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 1);
testFunctionCall(
calls.at(0),
Mode::SingleLine,
"f(string)",
false,
fmt::encodeDyn(string{"any"})
);
}
BOOST_AUTO_TEST_CASE(call_return_string)
{
char const* source = R"(
// f() -> 0x20, 3, "any"
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 1);
testFunctionCall(
calls.at(0),
Mode::SingleLine,
"f()",
false,
fmt::encodeArgs(),
fmt::encodeDyn(string{"any"})
);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple) BOOST_AUTO_TEST_CASE(call_arguments_tuple)
{ {
char const* source = R"( char const* source = R"(

View File

@ -35,6 +35,7 @@ static regex s_uintType{"(uint\\d*)"};
static regex s_intType{"(int\\d*)"}; static regex s_intType{"(int\\d*)"};
static regex s_bytesType{"(bytes\\d+)"}; static regex s_bytesType{"(bytes\\d+)"};
static regex s_dynBytesType{"(\\bbytes\\b)"}; static regex s_dynBytesType{"(\\bbytes\\b)"};
static regex s_stringType{"(string)"};
/// Translates Solidity's ABI types into the internal type representation of /// Translates Solidity's ABI types into the internal type representation of
/// soltest. /// soltest.
@ -55,6 +56,12 @@ auto contractABITypes(string const& _type) -> vector<ABIType>
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32}); abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::HexString, ABIType::AlignLeft, 32}); abiTypes.push_back(ABIType{ABIType::HexString, ABIType::AlignLeft, 32});
} }
else if (regex_match(_type, s_stringType))
{
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::UnsignedDec, ABIType::AlignRight, 32});
abiTypes.push_back(ABIType{ABIType::String, ABIType::AlignLeft, 32});
}
else else
abiTypes.push_back(ABIType{ABIType::None, ABIType::AlignRight, 0}); abiTypes.push_back(ABIType{ABIType::None, ABIType::AlignRight, 0});
return abiTypes; return abiTypes;
@ -319,6 +326,25 @@ string TestFunctionCall::formatBytesRange(
case ABIType::HexString: case ABIType::HexString:
os << "hex\"" << toHex(_bytes) << "\""; os << "hex\"" << toHex(_bytes) << "\"";
break; break;
case ABIType::String:
{
os << "\"";
bool expectZeros = false;
for (auto const& v: _bytes)
{
if (expectZeros && v != 0)
return {};
if (v == 0) expectZeros = true;
else
{
if (!isprint(v) || v == '"')
return {};
os << v;
}
}
os << "\"";
break;
}
case ABIType::Failure: case ABIType::Failure:
break; break;
case ABIType::None: case ABIType::None: