/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. solidity is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with solidity. If not, see . */ #pragma once #include #include #include #include #include #include #include #include #include namespace dev { namespace solidity { namespace test { /** * All soltest tokens. */ #define SOLT_TOKEN_LIST(T, K) \ T(Unknown, "unknown", 0) \ T(Invalid, "invalid", 0) \ T(EOS, "EOS", 0) \ T(Whitespace, "_", 0) \ /* punctuations */ \ T(LParen, "(", 0) \ T(RParen, ")", 0) \ T(LBrack, "[", 0) \ T(RBrack, "]", 0) \ T(LBrace, "{", 0) \ T(RBrace, "}", 0) \ T(Sub, "-", 0) \ T(Colon, ":", 0) \ T(Comma, ",", 0) \ T(Period, ".", 0) \ T(Arrow, "->", 0) \ T(Newline, "//", 0) \ /* Literals & identifier */ \ T(Comment, "#", 0) \ T(Number, "number", 0) \ T(HexNumber, "hex_number", 0) \ T(Identifier, "identifier", 0) \ /* type keywords */ \ K(Ether, "ether", 0) \ K(Hex, "hex", 0) \ K(Boolean, "boolean", 0) \ /* special keywords */ \ K(Left, "left", 0) \ K(Right, "right", 0) \ K(Failure, "FAILURE", 0) \ namespace soltest { enum class Token : unsigned int { #define T(name, string, precedence) name, SOLT_TOKEN_LIST(T, T) NUM_TOKENS #undef T }; /// Prints a friendly string representation of \param _token. inline std::string formatToken(Token _token) { switch (_token) { #define T(name, string, precedence) case Token::name: return string; SOLT_TOKEN_LIST(T, T) #undef T default: // Token::NUM_TOKENS: return ""; } } } /** * The purpose of the ABI type is the storage of type information * retrieved while parsing a test. This information is used * for the conversion of human-readable function arguments and * return values to `bytes` and vice-versa. * Defaults to None, a 0-byte representation. 0-bytes * can also be interpreted as Failure, which means * either a REVERT or another EVM failure. */ struct ABIType { enum Type { UnsignedDec, SignedDec, Boolean, Hex, HexString, Failure, None }; enum Align { AlignLeft, AlignRight, AlignNone, }; Type type = ABIType::None; Align align = ABIType::AlignRight; size_t size = 0; bool alignDeclared = false; }; /** * Helper that can hold format information retrieved * while scanning through a parameter list in soltest. */ struct FormatInfo { bool newline = false; }; /** * Parameter abstraction used for the encoding and decoding of * function parameter and expectation / return value lists. * A parameter list is usually a comma-separated list of literals. * It should not be possible to call create a parameter holding * an identifier, but if so, the ABI type would be invalid. */ struct Parameter { /// ABI encoded / decoded `bytes` of values. /// These `bytes` are used to pass values to function calls /// and also to store expected return vales. These are /// compared to the actual result of a function call /// and used for validating it. bytes rawBytes; /// Stores the raw string representation of this parameter. /// Used to print the unformatted arguments of a function call. std::string rawString; /// Types that were used to encode `rawBytes`. Expectations /// are usually comma separated literals. Their type is auto- /// detected and retained in order to format them later on. ABIType abiType; /// Format info attached to the parameter. It handles newlines given /// in the declaration of it. FormatInfo format; }; using ParameterList = std::vector; /** * Represents the expected result of a function call after it has been executed. This may be a single * return value or a comma-separated list of return values. It also contains the detected input * formats used to convert the values to `bytes` needed for the comparison with the actual result * of a call. In addition to that, it also stores the expected transaction status. * An optional comment can be assigned. */ struct FunctionCallExpectations { /// Representation of the comma-separated (or empty) list of expected result values /// attached to the function call object. It is checked against the actual result of /// a function call when used in test framework. ParameterList result; /// Expected status of the transaction. It can be either /// a REVERT or a different EVM failure (e.g. out-of-gas). bool failure = true; /// A Comment that can be attached to the expectations, /// that is retained and can be displayed. std::string comment; /// ABI encoded `bytes` of parsed expected return values. It is checked /// against the actual result of a function call when used in test framework. bytes rawBytes() const { bytes raw; for (auto const& param: result) raw += param.rawBytes; return raw; } }; /** * Represents the arguments passed to a function call. This can be a single * argument or a comma-separated list of arguments. It also contains the detected input * formats used to convert the arguments to `bytes` needed for the call. * An optional comment can be assigned. */ struct FunctionCallArgs { /// Types that were used to encode `rawBytes`. Parameters /// are usually comma separated literals. Their type is auto- /// detected and retained in order to format them later on. ParameterList parameters; /// A Comment that can be attached to the expectations, /// that is retained and can be displayed. std::string comment; /// ABI encoded `bytes` of parsed parameters. These `bytes` /// passed to the function call. bytes rawBytes() const { bytes raw; for (auto const& param: parameters) raw += param.rawBytes; return raw; } }; /** * Represents a function call read from an input stream. It contains the signature, the * arguments, an optional ether value and an expected execution result. */ struct FunctionCall { /// Signature of the function call, e.g. `f(uint256, uint256)`. std::string signature; /// Optional `ether` value that can be send with the call. u256 value; /// Object that holds all function parameters in their `bytes` /// representations given by the contract ABI. FunctionCallArgs arguments; /// Object that holds all function call expectation in /// their `bytes` representations given by the contract ABI. /// They are checked against the actual results and their /// `bytes` representation, as well as the transaction status. FunctionCallExpectations expectations; /// single / multi-line mode will be detected as follows: /// every newline (//) in source results in a function call /// that has its display mode set to multi-mode. Function and /// result parameter lists are an exception: a single parameter /// stores a format information that contains a newline definition. enum DisplayMode { SingleLine, MultiLine }; DisplayMode displayMode = DisplayMode::SingleLine; }; /** * Class that is able to parse an additional and well-formed comment section in a Solidity * source file used by the file-based unit test environment. For now, it parses function * calls and their expected result after the call was made. * * - Function calls defined in blocks: * // f(uint256, uint256): 1, 1 # Signature and comma-separated list of arguments # * // -> 1, 1 # Expected result value # * // g(), 2 ether # (Optional) Ether to be send with the call # * // -> 2, 3 * // h(uint256), 1 ether: 42 * // -> FAILURE # If REVERT or other EVM failure was detected # * ... */ class TestFileParser { public: /// Constructor that takes an input stream \param _stream to operate on /// and creates the internal scanner. TestFileParser(std::istream& _stream): m_scanner(_stream) {} /// Parses function calls blockwise and returns a list of function calls found. /// Throws an exception if a function call cannot be parsed because of its /// incorrect structure, an invalid or unsupported encoding /// of its arguments or expected results. std::vector parseFunctionCalls(); private: using Token = soltest::Token; /** * Token scanner that is used internally to abstract away character traversal. */ class Scanner { public: /// Constructor that takes an input stream \param _stream to operate on. /// It reads all lines into one single line, keeping the newlines. Scanner(std::istream& _stream) { readStream(_stream); } /// Reads input stream into a single line and resets the current iterator. void readStream(std::istream& _stream); /// Reads character stream and creates token. void scanNextToken(); soltest::Token currentToken() { return m_currentToken.first; } std::string currentLiteral() { return m_currentToken.second; } std::string scanComment(); std::string scanIdentifierOrKeyword(); std::string scanDecimalNumber(); std::string scanHexNumber(); private: using TokenDesc = std::pair; /// Advances current position in the input stream. void advance() { ++m_char; } /// Returns the current character. char current() const { return *m_char; } /// Peeks the next character. char peek() const { auto it = m_char; return *(it + 1); } /// Returns true if the end of a line is reached, false otherwise. bool isEndOfLine() const { return m_char == m_line.end(); } std::string m_line; std::string::iterator m_char; std::string m_currentLiteral; TokenDesc m_currentToken; }; bool accept(soltest::Token _token, bool const _expect = false); bool expect(soltest::Token _token, bool const _advance = true); /// Parses a function call signature in the form of f(uint256, ...). std::string parseFunctionSignature(); /// Parses the optional ether value that can be passed alongside the /// function call arguments. Throws an InvalidEtherValueEncoding exception /// if given value cannot be converted to `u256`. u256 parseFunctionCallValue(); /// Parses a comma-separated list of arguments passed with a function call. /// Does not check for a potential mismatch between the signature and the number /// or types of arguments. FunctionCallArgs parseFunctionCallArguments(); /// Parses the expected result of a function call execution. FunctionCallExpectations parseFunctionCallExpectations(); /// Parses the next parameter in a comma separated list. /// Takes a newly parsed, and type-annotated `bytes` argument, /// appends it to the internal `bytes` buffer of the parameter. It can also /// store newlines found in the source, that are needed to /// format input and output of the interactive update. Parameter parseParameter(); /// Parses and converts the current literal to its byte representation and /// preserves the chosen ABI type, as well as a raw, unformatted string representation /// of this literal. /// Based on the type information retrieved, the driver of this parser may format arguments, /// expectations and results. Supported types: /// - unsigned and signed decimal number literals. /// Returns invalid ABI type for empty literal. This is needed in order /// to detect empty expectations. Throws a ParserError if data is encoded incorrectly or /// if data type is not supported. std::tuple parseABITypeLiteral(); /// Recursively parses an identifier or a tuple definition that contains identifiers /// and / or parentheses like `((uint, uint), (uint, (uint, uint)), uint)`. std::string parseIdentifierOrTuple(); /// Parses a boolean literal. std::string parseBoolean(); /// Parses a comment that is defined like this: /// # A nice comment. # std::string parseComment(); /// Parses the current decimal number literal. std::string parseDecimalNumber(); /// Parses the current hex number literal. std::string parseHexNumber(); /// Tries to convert \param _literal to an unpadded `bytes` /// representation of the boolean number literal. Throws if conversion fails. bytes convertBoolean(std::string const& _literal); /// Tries to convert \param _literal to an unpadded `bytes` /// representation of the decimal number literal. Throws if conversion fails. bytes convertNumber(std::string const& _literal); /// Tries to convert \param _literal to an unpadded `bytes` /// representation of the hex literal. Throws if conversion fails. bytes convertHexNumber(std::string const& _literal); /// Tries to convert \param _literal to left-aligned, unpadded `bytes` /// representation of the hex string literal. Throws if conversion fails. bytes convertHexString(std::string const& _literal); /// A scanner instance Scanner m_scanner; }; } } }