diff --git a/Changelog.md b/Changelog.md index 8f239f58a..c537e1b19 100644 --- a/Changelog.md +++ b/Changelog.md @@ -41,9 +41,11 @@ Compiler Features: Language Features: * Allow to obtain the selector of public or external library functions via a member ``.selector``. + * Parser: Allow splitting string and hexadecimal string literals into multiple parts. Compiler Features: + * Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable. Bugfixes: diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index c4e684ce1..9ba3c4c85 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -4,6 +4,25 @@ Safe Remote Purchase ******************** +Purchasing goods remotely currently requires multiple parties that need to trust each other. +The simplest configuration involves a seller and a buyer. The buyer would like to receive +an item from the seller and the seller would like to get money (or an equivalent) +in return. The problematic part is the shipment here: There is no way to determine for +sure that the item arrived at the buyer. + +There are multiple ways to solve this problem, but all fall short in one or the other way. +In the following example, both parties have to put twice the value of the item into the +contract as escrow. As soon as this happened, the money will stay locked inside +the contract until the buyer confirms that they received the item. After that, +the buyer is returned the value (half of their deposit) and the seller gets three +times the value (their deposit plus the value). The idea behind +this is that both parties have an incentive to resolve the situation or otherwise +their money is locked forever. + +This contract of course does not solve the problem, but gives an overview of how +you can use state machine-like constructs inside a contract. + + :: pragma solidity >=0.4.22 <0.7.0; @@ -12,7 +31,7 @@ Safe Remote Purchase uint public value; address payable public seller; address payable public buyer; - enum State { Created, Locked, Inactive } + enum State { Created, Locked, Release, Inactive } // The state variable has a default value of the first member, `State.created` State public state; @@ -57,6 +76,7 @@ Safe Remote Purchase event Aborted(); event PurchaseConfirmed(); event ItemReceived(); + event SellerRefunded(); /// Abort the purchase and reclaim the ether. /// Can only be called by the seller before @@ -68,6 +88,10 @@ Safe Remote Purchase { emit Aborted(); state = State.Inactive; + // We use transfer here directly. It is + // reentrancy-safe, because it is the + // last call in this function and we + // already changed the state. seller.transfer(address(this).balance); } @@ -97,12 +121,24 @@ Safe Remote Purchase // It is important to change the state first because // otherwise, the contracts called using `send` below // can call in again here. - state = State.Inactive; - - // NOTE: This actually allows both the buyer and the seller to - // block the refund - the withdraw pattern should be used. + state = State.Release; buyer.transfer(value); - seller.transfer(address(this).balance); + } + + /// This function refunds the seller, i.e. + /// pays back the locked funds of the seller. + function refundSeller() + public + onlySeller + inState(State.Release) + { + emit SellerRefunded(); + // It is important to change the state first because + // otherwise, the contracts called using `send` below + // can call in again here. + state = State.Inactive; + + seller.transfer(3 * value); } } \ No newline at end of file diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 53a94da40..760f82064 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -464,7 +464,7 @@ a non-rational number). String Literals and Types ------------------------- -String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. +String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``), and they can also be split into multiple consecutive parts (``"foo" "bar"`` is equivalent to ``"foobar"``) which can be helpful when dealing with long strings. They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. For example, with ``bytes32 samevar = "stringliteral"`` the string literal is interpreted in its raw byte form when assigned to a ``bytes32`` type. @@ -503,7 +503,14 @@ terminate the string literal. Newline only terminates the string literal if it i Hexadecimal Literals -------------------- -Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``, ``hex'0011_22_FF'``). Their content must be hexadecimal digits which can optionally use a single underscore as separator between byte boundaries. The value of the literal will be the binary representation of the hexadecimal sequence. +Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double +or single-quotes (``hex"001122FF"``, ``hex'0011_22_FF'``). Their content must be +hexadecimal digits which can optionally use a single underscore as separator between +byte boundaries. The value of the literal will be the binary representation +of the hexadecimal sequence. + +Multiple hexadecimal literals separated by whitespace are concatenated into a single literal: +``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"`` Hexadecimal literals behave like :ref:`string literals ` and have the same convertibility restrictions. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 8b8c06735..6c625e997 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -262,7 +262,15 @@ Contract Related the current contract, explicitly convertible to :ref:`address` ``selfdestruct(address payable recipient)``: - destroy the current contract, sending its funds to the given :ref:`address` + Destroy the current contract, sending its funds to the given :ref:`address` + and end execution. + Note that ``selfdestruct`` has some peculiarities inherited from the EVM: + + - the receiving contract's receive function is not executed. + - the contract is only really destroyed at the end of the transaction and ``revert`` s might "undo" the destruction. + + + Furthermore, all functions of the current contract are callable directly including the current function. diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 1b0e97204..1299939f5 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -23,32 +23,59 @@ #include #include #include +#include #include using namespace std; using namespace dev; +namespace +{ + +static char const* upperHexChars = "0123456789ABCDEF"; +static char const* lowerHexChars = "0123456789abcdef"; + +} + +string dev::toHex(uint8_t _data, HexCase _case) +{ + assertThrow(_case != HexCase::Mixed, BadHexCase, "Mixed case can only be used for byte arrays."); + + char const* chars = _case == HexCase::Upper ? upperHexChars : lowerHexChars; + + return std::string{ + chars[(unsigned(_data) / 16) & 0xf], + chars[unsigned(_data) & 0xf] + }; +} + string dev::toHex(bytes const& _data, HexPrefix _prefix, HexCase _case) { - std::ostringstream ret; - if (_prefix == HexPrefix::Add) - ret << "0x"; + std::string ret(_data.size() * 2 + (_prefix == HexPrefix::Add ? 2 : 0), 0); + size_t i = 0; + if (_prefix == HexPrefix::Add) + { + ret[i++] = '0'; + ret[i++] = 'x'; + } + + // Mixed case will be handled inside the loop. + char const* chars = _case == HexCase::Upper ? upperHexChars : lowerHexChars; int rix = _data.size() - 1; for (uint8_t c: _data) { // switch hex case every four hexchars - auto hexcase = std::nouppercase; - if (_case == HexCase::Upper) - hexcase = std::uppercase; - else if (_case == HexCase::Mixed) - hexcase = (rix-- & 2) == 0 ? std::nouppercase : std::uppercase; + if (_case == HexCase::Mixed) + chars = (rix-- & 2) == 0 ? lowerHexChars : upperHexChars; - ret << std::hex << hexcase << std::setfill('0') << std::setw(2) << size_t(c); + ret[i++] = chars[(unsigned(c) / 16) & 0xf]; + ret[i++] = chars[unsigned(c) & 0xf]; } + assertThrow(i == ret.size(), Exception, ""); - return ret.str(); + return ret; } int dev::fromHex(char _i, WhenError _throw) @@ -60,7 +87,7 @@ int dev::fromHex(char _i, WhenError _throw) if (_i >= 'A' && _i <= 'F') return _i - 'A' + 10; if (_throw == WhenError::Throw) - BOOST_THROW_EXCEPTION(BadHexCharacter() << errinfo_invalidSymbol(_i)); + assertThrow(false, BadHexCharacter, to_string(_i)); else return -1; } @@ -73,22 +100,18 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw) if (_s.size() % 2) { - int h = fromHex(_s[s++], WhenError::DontThrow); + int h = fromHex(_s[s++], _throw); if (h != -1) ret.push_back(h); - else if (_throw == WhenError::Throw) - BOOST_THROW_EXCEPTION(BadHexCharacter()); else return bytes(); } for (unsigned i = s; i < _s.size(); i += 2) { - int h = fromHex(_s[i], WhenError::DontThrow); - int l = fromHex(_s[i + 1], WhenError::DontThrow); + int h = fromHex(_s[i], _throw); + int l = fromHex(_s[i + 1], _throw); if (h != -1 && l != -1) ret.push_back((uint8_t)(h * 16 + l)); - else if (_throw == WhenError::Throw) - BOOST_THROW_EXCEPTION(BadHexCharacter()); else return bytes(); } @@ -155,3 +178,15 @@ bool dev::isValidDecimal(string const& _string) return false; return true; } + +// Returns a quoted string if all characters are printable ASCII chars, +// or its hex representation otherwise. +std::string dev::formatAsStringOrNumber(std::string const& _value) +{ + if (_value.length() <= 32) + for (auto const& c: _value) + if (c <= 0x1f || c >= 0x7f || c == '"') + return "0x" + h256(_value, h256::AlignLeft).hex(); + + return "\"" + _value + "\""; +} diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 274365649..439134cc2 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -169,9 +169,12 @@ enum class HexCase Mixed = 2, }; -/// Convert a series of bytes to the corresponding string of hex duplets. -/// @param _w specifies the width of the first of the elements. Defaults to two - enough to represent a byte. -/// @example toHex("A\x69") == "4169" +/// Convert a single byte to a string of hex characters (of length two), +/// optionally with uppercase hex letters. +std::string toHex(uint8_t _data, HexCase _case = HexCase::Lower); + +/// Convert a series of bytes to the corresponding string of hex duplets, +/// optionally with "0x" prefix and with uppercase hex letters. std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower); /// Converts a (printable) ASCII hex character into the corresponding integer value. @@ -246,10 +249,6 @@ inline bytes toCompactBigEndian(T _val, unsigned _min = 0) toBigEndian(_val, ret); return ret; } -inline bytes toCompactBigEndian(uint8_t _val, unsigned _min = 0) -{ - return (_min || _val) ? bytes{ _val } : bytes{}; -} /// Convenience function for conversion of a u256 to hex inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd) @@ -258,13 +257,18 @@ inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd) return (prefix == HexPrefix::Add) ? "0x" + str : str; } +inline std::string toCompactHexWithPrefix(u256 const& _value) +{ + return toHex(toCompactBigEndian(_value, 1), HexPrefix::Add); +} + /// Returns decimal representation for small numbers and hex for large numbers. inline std::string formatNumber(bigint const& _value) { if (_value < 0) return "-" + formatNumber(-_value); if (_value > 0x1000000) - return toHex(toCompactBigEndian(_value), HexPrefix::Add); + return toHex(toCompactBigEndian(_value, 1), HexPrefix::Add); else return _value.str(); } @@ -272,17 +276,11 @@ inline std::string formatNumber(bigint const& _value) inline std::string formatNumber(u256 const& _value) { if (_value > 0x1000000) - return toHex(toCompactBigEndian(_value), HexPrefix::Add); + return toCompactHexWithPrefix(_value); else return _value.str(); } -inline std::string toCompactHexWithPrefix(u256 val) -{ - std::ostringstream ret; - ret << std::hex << val; - return "0x" + ret.str(); -} // Algorithms for string and string-like collections. @@ -337,7 +335,6 @@ void iterateReplacing(std::vector& _vector, F const& _f) _vector = std::move(modifiedVector); } - namespace detail { template @@ -402,6 +399,10 @@ std::string getChecksummedAddress(std::string const& _addr); bool isValidHex(std::string const& _string); bool isValidDecimal(std::string const& _string); +/// @returns a quoted string if all characters are printable ASCII chars, +/// or its hex representation otherwise. +std::string formatAsStringOrNumber(std::string const& _value); + template bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare) { diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 943379d7a..df7a9ba01 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -46,11 +46,11 @@ private: DEV_SIMPLE_EXCEPTION(InvalidAddress); DEV_SIMPLE_EXCEPTION(BadHexCharacter); +DEV_SIMPLE_EXCEPTION(BadHexCase); DEV_SIMPLE_EXCEPTION(FileError); DEV_SIMPLE_EXCEPTION(DataTooLong); // error information to be added to exceptions -using errinfo_invalidSymbol = boost::error_info; using errinfo_comment = boost::error_info; } diff --git a/libdevcore/IpfsHash.cpp b/libdevcore/IpfsHash.cpp index add459a2c..22ad7100c 100644 --- a/libdevcore/IpfsHash.cpp +++ b/libdevcore/IpfsHash.cpp @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -55,11 +56,7 @@ string base58Encode(bytes const& _data) bytes dev::ipfsHash(string _data) { - if (_data.length() >= 1024 * 256) - BOOST_THROW_EXCEPTION( - DataTooLong() << - errinfo_comment("Ipfs hash for large (chunked) files not yet implemented.") - ); + assertThrow(_data.length() < 1024 * 256, DataTooLong, "IPFS hash for large (chunked) files not yet implemented."); bytes lengthAsVarint = varintEncoding(_data.size()); diff --git a/libdevcore/Visitor.h b/libdevcore/Visitor.h index c7f117d38..3d79e03e8 100644 --- a/libdevcore/Visitor.h +++ b/libdevcore/Visitor.h @@ -20,109 +20,41 @@ #pragma once -#include -#include - namespace dev { -/// Generic visitor used as follows: -/// std::visit(GenericVisitor( -/// [](Class1& _c) { _c.f(); }, -/// [](Class2& _c) { _c.g(); } -/// ), variant); -/// This one does not have a fallback and will fail at -/// compile-time if you do not specify all variants. +/** + * Generic visitor used as follows: + * std::visit(GenericVisitor{ + * [](Class1& _c) { _c.f(); }, + * [](Class2& _c) { _c.g(); } + * }, variant); + * This one does not have a fallback and will fail at + * compile-time if you do not specify all variants. + * + * Fallback with no return (it will not fail if you do not specify all variants): + * std::visit(GenericVisitor{ + * VisitorFallback<>{}, + * [](Class1& _c) { _c.f(); }, + * [](Class2& _c) { _c.g(); } + * }, variant); + * + * Fallback with return type R (the fallback returns `R{}`: + * std::visit(GenericVisitor{ + * VisitorFallback{}, + * [](Class1& _c) { _c.f(); }, + * [](Class2& _c) { _c.g(); } + * }, variant); + */ -template -struct GenericVisitor{}; +template struct VisitorFallback; -template -struct GenericVisitor: public GenericVisitor -{ - using GenericVisitor::operator (); - explicit GenericVisitor( - std::function _visitor, - std::function... _otherVisitors - ): - GenericVisitor(std::move(_otherVisitors)...), - m_visitor(std::move(_visitor)) - {} +template +struct VisitorFallback { template R operator()(T&&) const { return {}; } }; - void operator()(Visitable& _v) const { m_visitor(_v); } - - std::function m_visitor; -}; -template <> -struct GenericVisitor<>: public boost::static_visitor<> { - void operator()() const {} -}; - -/// Generic visitor with fallback: -/// std::visit(GenericFallbackVisitor( -/// [](Class1& _c) { _c.f(); }, -/// [](Class2& _c) { _c.g(); } -/// ), variant); -/// This one DOES have a fallback and will NOT fail at -/// compile-time if you do not specify all variants. - -template -struct GenericFallbackVisitor{}; - -template -struct GenericFallbackVisitor: public GenericFallbackVisitor -{ - explicit GenericFallbackVisitor( - std::function _visitor, - std::function... _otherVisitors - ): - GenericFallbackVisitor(std::move(_otherVisitors)...), - m_visitor(std::move(_visitor)) - {} - - using GenericFallbackVisitor::operator (); - void operator()(Visitable& _v) const { m_visitor(_v); } - - std::function m_visitor; -}; -template <> -struct GenericFallbackVisitor<>: public boost::static_visitor<> { - template - void operator()(T&) const { } -}; - -/// Generic visitor with fallback that can return a value: -/// std::visit(GenericFallbackReturnsVisitor( -/// [](Class1& _c) { return _c.f(); }, -/// [](Class2& _c) { return _c.g(); } -/// ), variant); -/// This one DOES have a fallback and will NOT fail at -/// compile-time if you do not specify all variants. -/// The fallback {}-constructs the return value. - -template -struct GenericFallbackReturnsVisitor{}; - -template -struct GenericFallbackReturnsVisitor: public GenericFallbackReturnsVisitor -{ - explicit GenericFallbackReturnsVisitor( - std::function _visitor, - std::function... _otherVisitors - ): - GenericFallbackReturnsVisitor(std::move(_otherVisitors)...), - m_visitor(std::move(_visitor)) - {} - - using GenericFallbackReturnsVisitor::operator (); - R operator()(Visitable& _v) const { return m_visitor(_v); } - - std::function m_visitor; -}; -template -struct GenericFallbackReturnsVisitor: public boost::static_visitor { - template - R operator()(T&) const { return {}; } -}; +template<> +struct VisitorFallback<> { template void operator()(T&&) const {} }; +template struct GenericVisitor: Visitors... { using Visitors::operator()...; }; +template GenericVisitor(Visitors...) -> GenericVisitor; } diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 56f2a6768..062ad672d 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -324,7 +324,7 @@ Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const collection.append(createJsonValue("PUSH data", i.location().start, i.location().end, toStringInHex(i.data()))); break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } } @@ -642,7 +642,7 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.push_back((uint8_t)Instruction::JUMPDEST); break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); } } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 61355530e..53d4b79a0 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -81,7 +81,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const default: break; } - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } int AssemblyItem::arguments() const @@ -212,7 +212,7 @@ string AssemblyItem::toAssemblyText() const assertThrow(false, AssemblyException, "Invalid assembly item."); break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) { @@ -277,7 +277,7 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " ???"; break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } return _out; } diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp index 949d2c75a..c8e2f9210 100644 --- a/libevmasm/CommonSubexpressionEliminator.cpp +++ b/libevmasm/CommonSubexpressionEliminator.cpp @@ -158,11 +158,10 @@ AssemblyItems CSECodeGenerator::generateCode( for (auto id: {p.first, p.second}) if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) { - if (seqNr < _initialSequenceNumber) - // Invalid sequenced operation. - // @todo quick fix for now. Proper fix needs to choose representative with higher - // sequence number during dependency analysis. - BOOST_THROW_EXCEPTION(StackTooDeepException()); + // Invalid sequenced operation. + // @todo quick fix for now. Proper fix needs to choose representative with higher + // sequence number during dependency analysis. + assertThrow(seqNr >= _initialSequenceNumber, StackTooDeepException, ""); sequencedExpressions.insert(make_pair(seqNr, id)); } @@ -222,12 +221,9 @@ void CSECodeGenerator::addDependencies(Id _c) return; // we already computed the dependencies for _c ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); assertThrow(expr.item, OptimizerException, ""); - if (expr.item->type() == UndefinedItem) - BOOST_THROW_EXCEPTION( - // If this exception happens, we need to find a different way to generate the - // compound expression. - ItemNotAvailableException() << errinfo_comment("Undefined item requested but not available.") - ); + // If this exception happens, we need to find a different way to generate the + // compound expression. + assertThrow(expr.item->type() != UndefinedItem, ItemNotAvailableException, "Undefined item requested but not available."); for (Id argument: expr.arguments) { addDependencies(argument); diff --git a/libevmasm/Exceptions.h b/libevmasm/Exceptions.h index 06b0ac788..d6557d399 100644 --- a/libevmasm/Exceptions.h +++ b/libevmasm/Exceptions.h @@ -33,5 +33,8 @@ struct OptimizerException: virtual AssemblyException {}; struct StackTooDeepException: virtual OptimizerException {}; struct ItemNotAvailableException: virtual OptimizerException {}; +DEV_SIMPLE_EXCEPTION(InvalidDeposit); +DEV_SIMPLE_EXCEPTION(InvalidOpcode); + } } diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index f2f5879d9..c93fbf158 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -31,9 +31,6 @@ namespace dev namespace eth { -DEV_SIMPLE_EXCEPTION(InvalidDeposit); -DEV_SIMPLE_EXCEPTION(InvalidOpcode); - /// Virtual machine bytecode instruction. enum class Instruction: uint8_t { diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index bcbbb2db6..4d5b34b9f 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -810,7 +810,7 @@ Token Scanner::scanHexString() literal.complete(); advance(); // consume quote - return Token::StringLiteral; + return Token::HexStringLiteral; } // Parse for regex [:digit:]+(_[:digit:]+)* diff --git a/liblangutil/Token.h b/liblangutil/Token.h index 4401028da..98d832fe3 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -226,6 +226,7 @@ namespace langutil K(FalseLiteral, "false", 0) \ T(Number, nullptr, 0) \ T(StringLiteral, nullptr, 0) \ + T(HexStringLiteral, nullptr, 0) \ T(CommentLiteral, nullptr, 0) \ \ /* Identifiers (not keywords or future reserved words). */ \ diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index a858bedcd..54ebba602 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -851,6 +851,7 @@ string ASTJsonConverter::literalTokenKind(Token _token) case dev::solidity::Token::Number: return "number"; case dev::solidity::Token::StringLiteral: + case dev::solidity::Token::HexStringLiteral: return "string"; case dev::solidity::Token::TrueLiteral: case dev::solidity::Token::FalseLiteral: diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 396222c3e..e36710e90 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -347,6 +347,7 @@ TypePointer TypeProvider::forLiteral(Literal const& _literal) case Token::Number: return rationalNumber(_literal); case Token::StringLiteral: + case Token::HexStringLiteral: return stringLiteral(_literal.value()); default: return nullptr; diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 03dc40c5b..224d55449 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -975,7 +976,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( for (size_t i = 0; i < words; ++i) { wordParams[i]["offset"] = to_string(i * 32); - wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex(); + wordParams[i]["wordValue"] = formatAsStringOrNumber(value.substr(32 * i, 32)); } templ("word", wordParams); return templ.render(); @@ -990,7 +991,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( } )"); templ("functionName", functionName); - templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex()); + templ("wordValue", formatAsStringOrNumber(value)); return templ.render(); } }); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 91fdde0e9..30a0039b3 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -23,6 +23,8 @@ #include #include #include + +#include #include #include @@ -1807,7 +1809,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const for (size_t i = 0; i < words; ++i) { wordParams[i]["offset"] = to_string(32 + i * 32); - wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex(); + wordParams[i]["wordValue"] = formatAsStringOrNumber(data.substr(32 * i, 32)); } templ("word", wordParams); return templ.render(); diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index fcafe7bf6..bbf43b2bb 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1783,9 +1783,22 @@ ASTPointer Parser::parsePrimaryExpression() } break; case Token::StringLiteral: + case Token::HexStringLiteral: + { + string literal = m_scanner->currentLiteral(); + Token firstToken = m_scanner->currentToken(); + while (m_scanner->peekNextToken() == firstToken) + { + m_scanner->next(); + literal += m_scanner->currentLiteral(); + } nodeFactory.markEndPosition(); - expression = nodeFactory.createNode(token, getLiteralAndAdvance()); + m_scanner->next(); + if (m_scanner->currentToken() == Token::Illegal) + fatalParserError(to_string(m_scanner->currentError())); + expression = nodeFactory.createNode(token, make_shared(literal)); break; + } case Token::Identifier: nodeFactory.markEndPosition(); expression = nodeFactory.createNode(getLiteralAndAdvance()); diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 626431005..12b5c7940 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -130,7 +130,7 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) solAssert(!_identifier.name.empty(), ""); size_t numErrorsBefore = m_errorReporter.errors().size(); bool success = true; - if (m_currentScope->lookup(_identifier.name, Scope::Visitor( + if (m_currentScope->lookup(_identifier.name, GenericVisitor{ [&](Scope::Variable const& _var) { if (!m_activeVariables.count(&_var)) @@ -155,7 +155,7 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) ); success = false; } - ))) + })) { } else @@ -304,7 +304,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) if (f->literalArguments) needsLiteralArguments = true; } - else if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( + else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ [&](Scope::Variable const&) { m_errorReporter.typeError( @@ -327,7 +327,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) parameters = _fun.arguments.size(); returns = _fun.returns.size(); } - ))) + })) { if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location)) m_errorReporter.declarationError(_funCall.functionName.location, "Function not found."); diff --git a/libyul/AsmScope.h b/libyul/AsmScope.h index e8cd6ef64..9e3a74d03 100644 --- a/libyul/AsmScope.h +++ b/libyul/AsmScope.h @@ -48,8 +48,6 @@ struct Scope }; using Identifier = std::variant; - using Visitor = dev::GenericVisitor; - using NonconstVisitor = dev::GenericVisitor; bool registerVariable(YulString _name, YulType const& _type); bool registerLabel(YulString _name); diff --git a/libyul/ObjectParser.cpp b/libyul/ObjectParser.cpp index 5f1eadef6..2e201046f 100644 --- a/libyul/ObjectParser.cpp +++ b/libyul/ObjectParser.cpp @@ -120,7 +120,10 @@ void ObjectParser::parseData(Object& _containingObject) YulString name = parseUniqueName(&_containingObject); - expectToken(Token::StringLiteral, false); + if (currentToken() == Token::HexStringLiteral) + expectToken(Token::HexStringLiteral, false); + else + expectToken(Token::StringLiteral, false); addNamedSubObject(_containingObject, name, make_shared(name, asBytes(currentLiteral()))); advance(); } diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index 999d215a3..ca0aeb86a 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -207,11 +207,11 @@ void CodeGenerator::assemble( } catch (StackTooDeepError const& _e) { - BOOST_THROW_EXCEPTION( - InternalCompilerError() << errinfo_comment( - "Stack too deep when compiling inline assembly" + - (_e.comment() ? ": " + *_e.comment() : ".") - )); + solAssert( + false, + "Stack too deep when compiling inline assembly" + + (_e.comment() ? ": " + *_e.comment() : ".") + ); } solAssert(transform.stackErrors().empty(), "Stack errors present but not thrown."); } diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index bc92d4a80..a177c10fb 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -82,14 +82,14 @@ void VariableReferenceCounter::operator()(Block const& _block) void VariableReferenceCounter::increaseRefIfFound(YulString _variableName) { - m_scope->lookup(_variableName, Scope::Visitor( + m_scope->lookup(_variableName, GenericVisitor{ [=](Scope::Variable const& _var) { ++m_context.variableReferences[&_var]; }, [=](Scope::Label const&) { }, [=](Scope::Function const&) { } - )); + }); } CodeTransform::CodeTransform( @@ -283,11 +283,11 @@ void CodeTransform::operator()(FunctionCall const& _call) } Scope::Function* function = nullptr; - solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor( + solAssert(m_scope->lookup(_call.functionName.name, GenericVisitor{ [=](Scope::Variable&) { solAssert(false, "Expected function name."); }, [=](Scope::Label&) { solAssert(false, "Expected function name."); }, [&](Scope::Function& _function) { function = &_function; } - )), "Function name not found."); + }), "Function name not found."); solAssert(function, ""); solAssert(function->arguments.size() == _call.arguments.size(), ""); for (auto const& arg: _call.arguments | boost::adaptors::reversed) @@ -310,7 +310,7 @@ void CodeTransform::operator()(Identifier const& _identifier) m_assembly.setSourceLocation(_identifier.location); // First search internals, then externals. solAssert(m_scope, ""); - if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor( + if (m_scope->lookup(_identifier.name, GenericVisitor{ [=](Scope::Variable& _var) { // TODO: opportunity for optimization: Do not DUP if this is the last reference @@ -330,7 +330,7 @@ void CodeTransform::operator()(Identifier const& _identifier) { solAssert(false, "Function not removed during desugaring."); } - ))) + })) { return; } @@ -646,14 +646,14 @@ void CodeTransform::operator()(Block const& _block) AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier) { AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1); - if (!m_scope->lookup(_identifier.name, Scope::NonconstVisitor( + if (!m_scope->lookup(_identifier.name, GenericVisitor{ [=](Scope::Variable&) { solAssert(false, "Expected label"); }, [&](Scope::Label& _label) { label = labelID(_label); }, [=](Scope::Function&) { solAssert(false, "Expected label"); } - ))) + })) { solAssert(false, "Identifier not found."); } diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index e370126eb..39eca4630 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -190,7 +190,8 @@ void ControlFlowSimplifier::visit(Statement& _st) void ControlFlowSimplifier::simplify(std::vector& _statements) { - GenericFallbackReturnsVisitor const visitor( + GenericVisitor visitor{ + VisitorFallback{}, [&](If& _ifStmt) -> OptionalStatements { if (_ifStmt.body.statements.empty() && m_dialect.discardFunction()) { @@ -215,8 +216,7 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) return {}; } - ); - + }; iterateReplacing( _statements, [&](Statement& _stmt) -> OptionalStatements diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index 7c18dce95..02874eda1 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -160,17 +160,19 @@ void InlineModifier::operator()(Block& _block) std::optional> InlineModifier::tryInlineStatement(Statement& _statement) { // Only inline for expression statements, assignments and variable declarations. - Expression* e = std::visit(GenericFallbackReturnsVisitor( + Expression* e = std::visit(GenericVisitor{ + VisitorFallback{}, [](ExpressionStatement& _s) { return &_s.expression; }, [](Assignment& _s) { return _s.value.get(); }, [](VariableDeclaration& _s) { return _s.value.get(); } - ), _statement); + }, _statement); if (e) { // Only inline direct function calls. - FunctionCall* funCall = std::visit(GenericFallbackReturnsVisitor( + FunctionCall* funCall = std::visit(GenericVisitor{ + VisitorFallback{}, [](FunctionCall& _e) { return &_e; } - ), *e); + }, *e); if (funCall && m_driver.shallInline(*funCall, m_currentFunction)) return performInline(_statement, *funCall); } @@ -208,7 +210,8 @@ vector InlineModifier::performInline(Statement& _statement, FunctionC Statement newBody = BodyCopier(m_nameDispenser, variableReplacements)(function->body); newStatements += std::move(std::get(newBody).statements); - std::visit(GenericFallbackVisitor{ + std::visit(GenericVisitor{ + VisitorFallback<>{}, [&](Assignment& _assignment) { for (size_t i = 0; i < _assignment.variableNames.size(); ++i) diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 424caf575..fb97e22a8 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -71,7 +71,8 @@ void StructuralSimplifier::operator()(Block& _block) void StructuralSimplifier::simplify(std::vector& _statements) { - GenericFallbackReturnsVisitor const visitor( + GenericVisitor visitor{ + VisitorFallback{}, [&](If& _ifStmt) -> OptionalStatements { if (expressionAlwaysTrue(*_ifStmt.condition)) return {std::move(_ifStmt.body.statements)}; @@ -89,7 +90,7 @@ void StructuralSimplifier::simplify(std::vector& _statements) return {std::move(_forLoop.pre.statements)}; return {}; } - ); + }; iterateReplacing( _statements, diff --git a/libyul/optimiser/VarDeclInitializer.cpp b/libyul/optimiser/VarDeclInitializer.cpp index d6f07bc1e..abedfe069 100644 --- a/libyul/optimiser/VarDeclInitializer.cpp +++ b/libyul/optimiser/VarDeclInitializer.cpp @@ -30,7 +30,8 @@ void VarDeclInitializer::operator()(Block& _block) ASTModifier::operator()(_block); using OptionalStatements = std::optional>; - GenericFallbackReturnsVisitor visitor{ + GenericVisitor visitor{ + VisitorFallback{}, [](VariableDeclaration& _varDecl) -> OptionalStatements { if (_varDecl.value) @@ -51,5 +52,6 @@ void VarDeclInitializer::operator()(Block& _block) } } }; + iterateReplacing(_block.statements, [&](auto&& _statement) { return std::visit(visitor, _statement); }); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 387d6e515..b0c3b47da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -129,6 +129,8 @@ set(libyul_sources libyul/Common.cpp libyul/Common.h libyul/CompilabilityChecker.cpp + libyul/EWasmTranslationTest.cpp + libyul/EWasmTranslationTest.h libyul/FunctionSideEffects.cpp libyul/FunctionSideEffects.h libyul/Inliner.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index b75be49e7..591d1d7ed 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,7 @@ struct Testsuite Testsuite const g_interactiveTestsuites[] = { /* Title Path Subpath SMT NeedsVM Creator function */ + {"EWasm Translation", "libyul", "ewasmTranslationTests",false,false, &yul::test::EWasmTranslationTest::create}, {"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create}, {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, diff --git a/test/cmdlineTests/yul_string_format_ascii/input.json b/test/cmdlineTests/yul_string_format_ascii/input.json new file mode 100644 index 000000000..c23c65b5a --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (string memory) { return \"abcabc\"; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json new file mode 100644 index 000000000..40ed5700f --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -0,0 +1,161 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(64) + mstore(converted, 6) + + mstore(add(converted, 32), \"abcabc\") + + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value, pos) -> end { + let length := array_length_t_string_memory_ptr(value) + pos := array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) + copy_memory_to_memory(add(value, 0x20), pos, length) + end := add(pos, round_up_to_mul_of_32(length)) + } + + function abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + mstore(add(headStart, 0), sub(tail, headStart)) + tail := abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value0, tail) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function array_length_t_string_memory_ptr(value) -> length { + + + length := mload(value) + + + + } + + function array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) -> updated_pos { + mstore(pos, length) + updated_pos := add(pos, 0x20) + } + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(64) + mstore(converted, 6) + + mstore(add(converted, 32), \"abcabc\") + + } + + function copy_memory_to_memory(src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + function round_up_to_mul_of_32(value) -> result { + result := and(add(value, 31), not(31)) + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/input.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/input.json new file mode 100644 index 000000000..247f665cb --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (bytes32) { return \"abcabc\"; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json new file mode 100644 index 000000000..062cdfdc9 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -0,0 +1,114 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() -> converted { + converted := 0x6162636162630000000000000000000000000000000000000000000000000000 + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() + return_flag := 0 + break + + break + } + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_bytes32__to_t_bytes32__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_bytes32_to_t_bytes32_fromStack(value, pos) { + mstore(pos, cleanup_t_bytes32(value)) + } + + function abi_encode_tuple_t_bytes32__to_t_bytes32__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + abi_encode_t_bytes32_to_t_bytes32_fromStack(value0, add(headStart, 0)) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function cleanup_t_bytes32(value) -> cleaned { + cleaned := value + } + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() -> converted { + converted := 0x6162636162630000000000000000000000000000000000000000000000000000 + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() + return_flag := 0 + break + + break + } + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/input.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/input.json new file mode 100644 index 000000000..c7309f2af --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (bytes4) { return 0x61626364; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json new file mode 100644 index 000000000..7c163ff12 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -0,0 +1,138 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function cleanup_t_rational_1633837924_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_1633837924_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_1633837924_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0x61626364 + vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_bytes4_to_t_bytes4_fromStack(value, pos) { + mstore(pos, cleanup_t_bytes4(value)) + } + + function abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + abi_encode_t_bytes4_to_t_bytes4_fromStack(value0, add(headStart, 0)) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function cleanup_t_bytes4(value) -> cleaned { + cleaned := and(value, 0xffffffff00000000000000000000000000000000000000000000000000000000) + } + + function cleanup_t_rational_1633837924_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_1633837924_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_1633837924_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0x61626364 + vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_long/input.json b/test/cmdlineTests/yul_string_format_ascii_long/input.json new file mode 100644 index 000000000..cf3b2a854 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_long/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (string memory) { return \"abcdabcdcafecafeabcdabcdcafecafeffffzzzzoooo0123456789,.<,>.?:;'[{]}|`~!@#$%^&*()-_=+\"; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json new file mode 100644 index 000000000..8245d0dd9 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -0,0 +1,169 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(128) + mstore(converted, 85) + + mstore(add(converted, 32), \"abcdabcdcafecafeabcdabcdcafecafe\") + + mstore(add(converted, 64), \"ffffzzzzoooo0123456789,.<,>.?:;'\") + + mstore(add(converted, 96), \"[{]}|`~!@#$%^&*()-_=+\") + + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value, pos) -> end { + let length := array_length_t_string_memory_ptr(value) + pos := array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) + copy_memory_to_memory(add(value, 0x20), pos, length) + end := add(pos, round_up_to_mul_of_32(length)) + } + + function abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + mstore(add(headStart, 0), sub(tail, headStart)) + tail := abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value0, tail) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function array_length_t_string_memory_ptr(value) -> length { + + + length := mload(value) + + + + } + + function array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) -> updated_pos { + mstore(pos, length) + updated_pos := add(pos, 0x20) + } + + function convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(128) + mstore(converted, 85) + + mstore(add(converted, 32), \"abcdabcdcafecafeabcdabcdcafecafe\") + + mstore(add(converted, 64), \"ffffzzzzoooo0123456789,.<,>.?:;'\") + + mstore(add(converted, 96), \"[{]}|`~!@#$%^&*()-_=+\") + + } + + function copy_memory_to_memory(src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + function round_up_to_mul_of_32(value) -> result { + result := and(add(value, 31), not(31)) + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_hex/input.json b/test/cmdlineTests/yul_string_format_hex/input.json new file mode 100644 index 000000000..5ba723f56 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_hex/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (bytes4) { return 0xaabbccdd; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json new file mode 100644 index 000000000..38cbd08ea --- /dev/null +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -0,0 +1,138 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function cleanup_t_rational_2864434397_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_2864434397_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_2864434397_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0xaabbccdd + vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_bytes4_to_t_bytes4_fromStack(value, pos) { + mstore(pos, cleanup_t_bytes4(value)) + } + + function abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + abi_encode_t_bytes4_to_t_bytes4_fromStack(value0, add(headStart, 0)) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function cleanup_t_bytes4(value) -> cleaned { + cleaned := and(value, 0xffffffff00000000000000000000000000000000000000000000000000000000) + } + + function cleanup_t_rational_2864434397_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_2864434397_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_2864434397_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0xaabbccdd + vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp index 7e7b58469..5d97ee988 100644 --- a/test/libsolidity/SolidityScanner.cpp +++ b/test/libsolidity/SolidityScanner.cpp @@ -505,7 +505,7 @@ BOOST_AUTO_TEST_CASE(valid_hex_literal) { Scanner scanner(CharStream("{ hex\"00112233FF\"", "")); BOOST_CHECK_EQUAL(scanner.currentToken(), Token::LBrace); - BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral); + BOOST_CHECK_EQUAL(scanner.next(), Token::HexStringLiteral); BOOST_CHECK_EQUAL(scanner.currentLiteral(), std::string("\x00\x11\x22\x33\xFF", 5)); } diff --git a/test/libsolidity/semanticTests/viaYul/string_format.sol b/test/libsolidity/semanticTests/viaYul/string_format.sol new file mode 100644 index 000000000..2d9d71d7c --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/string_format.sol @@ -0,0 +1,13 @@ +contract C { + function f1() external pure returns (string memory) { return "abcabc"; } + function f2() external pure returns (string memory) { return "abcabc`~12345677890- _=+!@#$%^&*()[{]}|;:',<.>?"; } + function g() external pure returns (bytes32) { return "abcabc"; } + function h() external pure returns (bytes4) { return 0xcafecafe; } +} +// ==== +// compileViaYul: only +// ---- +// f1() -> 0x20, 6, left(0x616263616263) +// f2() -> 32, 47, 44048183223289766195424279195050628400112610419087780792899004030957505095210, 18165586057823232067963737336409268114628061002662705707816940456850361417728 +// g() -> left(0x616263616263) +// h() -> left(0xcafecafe) diff --git a/test/libsolidity/syntaxTests/string/string_multipart_hex_valid_parts.sol b/test/libsolidity/syntaxTests/string/string_multipart_hex_valid_parts.sol new file mode 100644 index 000000000..684322143 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_hex_valid_parts.sol @@ -0,0 +1,8 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"aa" hex"b"; + return escapeCharacters; + } +} +// ---- +// ParserError: (108-112): Expected even number of hex-nibbles within double-quotes. diff --git a/test/libsolidity/syntaxTests/string/string_multipart_newline_with_hex_prefix.sol b/test/libsolidity/syntaxTests/string/string_multipart_newline_with_hex_prefix.sol new file mode 100644 index 000000000..1d4de1759 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_newline_with_hex_prefix.sol @@ -0,0 +1,9 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"0000" + hex"deaf" + hex"feed"; + return escapeCharacters; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/string/string_multipart_newline_without_hex_prefix.sol b/test/libsolidity/syntaxTests/string/string_multipart_newline_without_hex_prefix.sol new file mode 100644 index 000000000..613b86da6 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_newline_without_hex_prefix.sol @@ -0,0 +1,10 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"0000" + "deaf" + "feed"; + return escapeCharacters; + } +} +// ---- +// ParserError: (118-124): Expected ';' but got 'StringLiteral' \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/string/string_multipart_only_hex.sol b/test/libsolidity/syntaxTests/string/string_multipart_only_hex.sol new file mode 100644 index 000000000..d748104f2 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_only_hex.sol @@ -0,0 +1,8 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"aa" hex"bb" "cc"; + return escapeCharacters; + } +} +// ---- +// ParserError: (116-120): Expected ';' but got 'StringLiteral' diff --git a/test/libsolidity/syntaxTests/string/string_multipart_only_regular.sol b/test/libsolidity/syntaxTests/string/string_multipart_only_regular.sol new file mode 100644 index 000000000..b420601f5 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_only_regular.sol @@ -0,0 +1,8 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = "foo" "bar" hex"aa"; + return escapeCharacters; + } +} +// ---- +// ParserError: (112-119): Expected ';' but got 'HexStringLiteral' diff --git a/test/libsolidity/syntaxTests/string/string_multipart_single_line.sol b/test/libsolidity/syntaxTests/string/string_multipart_single_line.sol new file mode 100644 index 000000000..c07bde30c --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_single_line.sol @@ -0,0 +1,7 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = "first" "second" "third"; + return escapeCharacters; + } +} +// ---- diff --git a/test/libyul/EWasmTranslationTest.cpp b/test/libyul/EWasmTranslationTest.cpp new file mode 100644 index 000000000..dc174ff7b --- /dev/null +++ b/test/libyul/EWasmTranslationTest.cpp @@ -0,0 +1,156 @@ +/* + 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 . +*/ + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +using namespace dev; +using namespace langutil; +using namespace yul; +using namespace yul::test; +using namespace dev::solidity; +using namespace dev::solidity::test; +using namespace std; + + +EWasmTranslationTest::EWasmTranslationTest(string const& _filename) +{ + boost::filesystem::path path(_filename); + + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + m_source = parseSourceAndSettings(file); + m_expectation = parseSimpleExpectations(file); +} + +TestCase::TestResult EWasmTranslationTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + if (!parse(_stream, _linePrefix, _formatted)) + return TestResult::FatalError; + + *m_object = EVMToEWasmTranslator( + EVMDialect::strictAssemblyForEVMObjects(dev::test::Options::get().evmVersion()) + ).run(*m_object); + + // Add call to "main()". + m_object->code->statements.emplace_back( + ExpressionStatement{{}, FunctionCall{{}, Identifier{{}, "main"_yulstring}, {}}} + ); + + m_obtainedResult = interpret(); + + if (m_expectation != m_obtainedResult) + { + string nextIndentLevel = _linePrefix + " "; + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl; + // TODO could compute a simple diff with highlighted lines + printIndented(_stream, m_expectation, nextIndentLevel); + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl; + printIndented(_stream, m_obtainedResult, nextIndentLevel); + return TestResult::Failure; + } + return TestResult::Success; +} + +void EWasmTranslationTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + printIndented(_stream, m_source, _linePrefix); +} + +void EWasmTranslationTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const +{ + printIndented(_stream, m_obtainedResult, _linePrefix); +} + +void EWasmTranslationTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + _stream << _linePrefix << line << endl; +} + +bool EWasmTranslationTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + AssemblyStack stack( + dev::test::Options::get().evmVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::none() + ); + if (stack.parseAndAnalyze("", m_source)) + { + m_object = stack.parserResult(); + return true; + } + else + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; + printErrors(_stream, stack.errors()); + return false; + } +} + +string EWasmTranslationTest::interpret() +{ + InterpreterState state; + state.maxTraceSize = 10000; + state.maxSteps = 10000; + WasmDialect dialect; + Interpreter interpreter(state, dialect); + try + { + interpreter(*m_object->code); + } + catch (InterpreterTerminatedGeneric const&) + { + } + + stringstream result; + state.dumpTraceAndState(result); + return result.str(); +} + +void EWasmTranslationTest::printErrors(ostream& _stream, ErrorList const& _errors) +{ + SourceReferenceFormatter formatter(_stream); + + for (auto const& error: _errors) + formatter.printErrorInformation(*error); +} diff --git a/test/libyul/EWasmTranslationTest.h b/test/libyul/EWasmTranslationTest.h new file mode 100644 index 000000000..351f83607 --- /dev/null +++ b/test/libyul/EWasmTranslationTest.h @@ -0,0 +1,65 @@ +/* + 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 + +namespace langutil +{ +class Scanner; +class Error; +using ErrorList = std::vector>; +} + +namespace yul +{ +namespace test +{ + +class EWasmTranslationTest: public dev::solidity::test::EVMVersionRestrictedTestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::unique_ptr(new EWasmTranslationTest(_config.filename)); + } + + explicit EWasmTranslationTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + + void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; + +private: + void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; + bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); + std::string interpret(); + + static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); + + std::string m_source; + std::string m_expectation; + + std::shared_ptr m_object; + std::string m_obtainedResult; +}; + +} +} diff --git a/test/libyul/ewasmTranslationTests/datacopy.yul b/test/libyul/ewasmTranslationTests/datacopy.yul new file mode 100644 index 000000000..8812e7c9b --- /dev/null +++ b/test/libyul/ewasmTranslationTests/datacopy.yul @@ -0,0 +1,20 @@ +object "main" +{ + code { + datacopy(0, and(dataoffset("main"), 15), and(datasize("main"), 15)) + datacopy(32, and(dataoffset("sub"), 15), and(datasize("sub"), 15)) + sstore(0, mload(0)) + sstore(1, mload(32)) + } + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 636f6465636f6465000000000000000000000000000000000000000000000000 +// 40: 6465636f00000000000000000000000000000000000000000000000000000000 +// 60: 636f6465636f6465000000000000000000000000000000000000000000000000 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 6465636f00000000000000000000000000000000000000000000000000000000 +// 0000000000000000000000000000000000000000000000000000000000000001: 636f6465636f6465000000000000000000000000000000000000000000000000 diff --git a/test/libyul/ewasmTranslationTests/dataoffset.yul b/test/libyul/ewasmTranslationTests/dataoffset.yul new file mode 100644 index 000000000..d9dc12c9c --- /dev/null +++ b/test/libyul/ewasmTranslationTests/dataoffset.yul @@ -0,0 +1,16 @@ +object "main" +{ + code { + sstore(0, dataoffset("main")) + sstore(1, dataoffset("sub")) + } + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 000000000000000000000000000000000000000000000000000000000000070c +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 000000000000000000000000000000000000000000000000000000000000006e +// 0000000000000000000000000000000000000000000000000000000000000001: 000000000000000000000000000000000000000000000000000000000000070c diff --git a/test/libyul/ewasmTranslationTests/datasize.yul b/test/libyul/ewasmTranslationTests/datasize.yul new file mode 100644 index 000000000..637c2c36d --- /dev/null +++ b/test/libyul/ewasmTranslationTests/datasize.yul @@ -0,0 +1,16 @@ +object "main" +{ + code { + sstore(0, datasize("main")) + sstore(1, datasize("sub")) + } + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0000000000000000000000000000000000000000000000000000000000000109 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000b64 +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000000000000000109 diff --git a/test/libyul/ewasmTranslationTests/mstore_mload.yul b/test/libyul/ewasmTranslationTests/mstore_mload.yul new file mode 100644 index 000000000..95646abe2 --- /dev/null +++ b/test/libyul/ewasmTranslationTests/mstore_mload.yul @@ -0,0 +1,14 @@ +{ + mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20) + mstore(0x40, mload(0x20)) + sstore(1, mload(0x40)) +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// 60: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// 80: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000001: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 diff --git a/test/libyul/ewasmTranslationTests/shl.yul b/test/libyul/ewasmTranslationTests/shl.yul new file mode 100644 index 000000000..f49092a30 --- /dev/null +++ b/test/libyul/ewasmTranslationTests/shl.yul @@ -0,0 +1,17 @@ +{ + let x := 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 + let y := shl(120, x) + let z := shr(136, y) + sstore(0, y) + sstore(1, z) +} +// ==== +// EVMVersion: >=constantinople +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0000000000000000000000000000000000101112131415161718191a1b1c1d1e +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 101112131415161718191a1b1c1d1e1f20000000000000000000000000000000 +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000101112131415161718191a1b1c1d1e diff --git a/test/libyul/ewasmTranslationTests/simple_mstore.yul b/test/libyul/ewasmTranslationTests/simple_mstore.yul new file mode 100644 index 000000000..dd9ac1d5e --- /dev/null +++ b/test/libyul/ewasmTranslationTests/simple_mstore.yul @@ -0,0 +1,8 @@ +{ + mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20) +} +// ---- +// Trace: +// Memory dump: +// 60: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// Storage dump: diff --git a/test/libyul/ewasmTranslationTests/simple_sstore.yul b/test/libyul/ewasmTranslationTests/simple_sstore.yul new file mode 100644 index 000000000..8fbb4923a --- /dev/null +++ b/test/libyul/ewasmTranslationTests/simple_sstore.yul @@ -0,0 +1,12 @@ +{ + sstore(1, 7) + sstore(2, sub(0, 1)) +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000002 +// 20: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000000000000000007 +// 0000000000000000000000000000000000000000000000000000000000000002: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/test/libyul/ewasmTranslationTests/smoke.yul b/test/libyul/ewasmTranslationTests/smoke.yul new file mode 100644 index 000000000..f7f1a1aef --- /dev/null +++ b/test/libyul/ewasmTranslationTests/smoke.yul @@ -0,0 +1,5 @@ +{} +// ---- +// Trace: +// Memory dump: +// Storage dump: diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index f02b092bd..70a79ffdf 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(isoltest ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerJSONTest.cpp ../libyul/Common.cpp + ../libyul/EWasmTranslationTest.cpp ../libyul/FunctionSideEffects.cpp ../libyul/ObjectCompilerTest.cpp ../libyul/YulOptimizerTest.cpp diff --git a/test/tools/yulInterpreter/CMakeLists.txt b/test/tools/yulInterpreter/CMakeLists.txt index 52fe0e3c6..02d53d697 100644 --- a/test/tools/yulInterpreter/CMakeLists.txt +++ b/test/tools/yulInterpreter/CMakeLists.txt @@ -1,6 +1,8 @@ set(sources EVMInstructionInterpreter.h EVMInstructionInterpreter.cpp + EWasmBuiltinInterpreter.h + EWasmBuiltinInterpreter.cpp Interpreter.h Interpreter.cpp ) diff --git a/test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp new file mode 100644 index 000000000..c52ee7e10 --- /dev/null +++ b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp @@ -0,0 +1,376 @@ +/* + 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 . +*/ +/** + * Yul interpreter module that evaluates EWasm builtins. + */ + +#include + +#include + +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; +using namespace yul::test; + +namespace +{ + +/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to +/// @a _target at offset @a _targetOffset. Behaves as if @a _source would +/// continue with an infinite sequence of zero bytes beyond its end. +void copyZeroExtended( + map& _target, bytes const& _source, + size_t _targetOffset, size_t _sourceOffset, size_t _size +) +{ + for (size_t i = 0; i < _size; ++i) + _target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0; +} + +} + +using u512 = boost::multiprecision::number>; + +u256 EWasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _arguments) +{ + vector arg; + for (u256 const& a: _arguments) + arg.emplace_back(uint64_t(a & uint64_t(-1))); + + if (_fun == "datasize"_yulstring) + return u256(keccak256(h256(_arguments.at(0)))) & 0xfff; + else if (_fun == "dataoffset"_yulstring) + return u256(keccak256(h256(_arguments.at(0) + 2))) & 0xfff; + else if (_fun == "datacopy"_yulstring) + { + // This is identical to codecopy. + if (accessMemory(_arguments.at(0), _arguments.at(2))) + copyZeroExtended( + m_state.memory, + m_state.code, + size_t(_arguments.at(0)), + size_t(_arguments.at(1) & size_t(-1)), + size_t(_arguments.at(2)) + ); + return 0; + } + else if (_fun == "drop"_yulstring) + return {}; + else if (_fun == "unreachable"_yulstring) + throw ExplicitlyTerminated(); + else if (_fun == "i64.add"_yulstring) + return arg[0] + arg[1]; + else if (_fun == "i64.sub"_yulstring) + return arg[0] - arg[1]; + else if (_fun == "i64.mul"_yulstring) + return arg[0] * arg[1]; + else if (_fun == "i64.div_u"_yulstring) + { + if (arg[1] == 0) + throw ExplicitlyTerminated(); + else + return arg[0] / arg[1]; + } + else if (_fun == "i64.rem_u"_yulstring) + { + if (arg[1] == 0) + throw ExplicitlyTerminated(); + else + return arg[0] % arg[1]; + } + else if (_fun == "i64.and"_yulstring) + return arg[0] & arg[1]; + else if (_fun == "i64.or"_yulstring) + return arg[0] | arg[1]; + else if (_fun == "i64.xor"_yulstring) + return arg[0] ^ arg[1]; + else if (_fun == "i64.shl"_yulstring) + return arg[0] << arg[1]; + else if (_fun == "i64.shr_u"_yulstring) + return arg[0] >> arg[1]; + else if (_fun == "i64.eq"_yulstring) + return arg[0] == arg[1] ? 1 : 0; + else if (_fun == "i64.ne"_yulstring) + return arg[0] != arg[1] ? 1 : 0; + else if (_fun == "i64.eqz"_yulstring) + return arg[0] == 0 ? 1 : 0; + else if (_fun == "i64.lt_u"_yulstring) + return arg[0] < arg[1] ? 1 : 0; + else if (_fun == "i64.gt_u"_yulstring) + return arg[0] > arg[1] ? 1 : 0; + else if (_fun == "i64.le_u"_yulstring) + return arg[0] <= arg[1] ? 1 : 0; + else if (_fun == "i64.ge_u"_yulstring) + return arg[0] >= arg[1] ? 1 : 0; + else if (_fun == "i64.store"_yulstring) + { + accessMemory(arg[0], 8); + writeMemoryWord(arg[0], arg[1]); + return 0; + } + else if (_fun == "i64.load"_yulstring) + { + accessMemory(arg[0], 8); + return readMemoryWord(arg[0]); + } + else if (_fun == "eth.getAddress"_yulstring) + return writeAddress(arg[0], m_state.address); + else if (_fun == "eth.getExternalBalance"_yulstring) + // TODO this does not read the address, but is consistent with + // EVM interpreter implementation. + // If we take the address into account, this needs to use readAddress. + return writeU128(arg[0], m_state.balance); + else if (_fun == "eth.getBlockHash"_yulstring) + { + if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber) + return 1; + else + return writeU256(arg[1], 0xaaaaaaaa + u256(arg[0] - m_state.blockNumber - 256)); + } + else if (_fun == "eth.call"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::CALL, {}); + return arg[0] & 1; + } + else if (_fun == "eth.callDataCopy"_yulstring) + { + if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.calldata.size()) + throw ExplicitlyTerminated(); + if (accessMemory(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.calldata, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return {}; + } + else if (_fun == "eth.getCallDataSize"_yulstring) + return m_state.calldata.size(); + else if (_fun == "eth.callCode"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::CALLCODE, {}); + return arg[0] & 1; + } + else if (_fun == "eth.callDelegate"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::DELEGATECALL, {}); + return arg[0] & 1; + } + else if (_fun == "eth.callStatic"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::STATICCALL, {}); + return arg[0] & 1; + } + else if (_fun == "eth.storageStore"_yulstring) + { + m_state.storage[h256(readU256(arg[0]))] = readU256((arg[1])); + return 0; + } + else if (_fun == "eth.storageLoad"_yulstring) + return writeU256(arg[1], m_state.storage[h256(readU256(arg[0]))]); + else if (_fun == "eth.getCaller"_yulstring) + // TODO should this only write 20 bytes? + return writeAddress(arg[0], m_state.caller); + else if (_fun == "eth.getCallValue"_yulstring) + return writeU128(arg[0], m_state.callvalue); + else if (_fun == "eth.codeCopy"_yulstring) + { + if (accessMemory(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.code, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return 0; + } + else if (_fun == "eth.getCodeSize"_yulstring) + return writeU256(arg[0], m_state.code.size()); + else if (_fun == "eth.getBlockCoinbase"_yulstring) + return writeAddress(arg[0], m_state.coinbase); + else if (_fun == "eth.create"_yulstring) + { + // TODO access memory + // TODO use writeAddress to store resulting address + logTrace(eth::Instruction::CREATE, {}); + return 0xcccccc + arg[1]; + } + else if (_fun == "eth.getBlockDifficulty"_yulstring) + return writeU256(arg[0], m_state.difficulty); + else if (_fun == "eth.externalCodeCopy"_yulstring) + { + // TODO use readAddress to read address. + if (accessMemory(arg[1], arg[3])) + // TODO this way extcodecopy and codecopy do the same thing. + copyZeroExtended( + m_state.memory, m_state.code, + size_t(arg[1]), size_t(arg[2]), size_t(arg[3]) + ); + return 0; + } + else if (_fun == "eth.getExternalCodeSize"_yulstring) + return u256(keccak256(h256(readAddress(arg[0])))) & 0xffffff; + else if (_fun == "eth.getGasLeft"_yulstring) + return 0x99; + else if (_fun == "eth.getBlockGasLimit"_yulstring) + return uint64_t(m_state.gaslimit); + else if (_fun == "eth.getTxGasPrice"_yulstring) + return writeU128(arg[0], m_state.gasprice); + else if (_fun == "eth.log"_yulstring) + { + logTrace(eth::Instruction::LOG0, {}); + return 0; + } + else if (_fun == "eth.getBlockNumber"_yulstring) + return m_state.blockNumber; + else if (_fun == "eth.getTxOrigin"_yulstring) + return writeAddress(arg[0], m_state.origin); + else if (_fun == "eth.finish"_yulstring) + { + bytes data; + if (accessMemory(arg[0], arg[1])) + data = readMemory(arg[0], arg[1]); + logTrace(eth::Instruction::RETURN, {}, data); + throw ExplicitlyTerminated(); + } + else if (_fun == "eth.revert"_yulstring) + { + bytes data; + if (accessMemory(arg[0], arg[1])) + data = readMemory(arg[0], arg[1]); + logTrace(eth::Instruction::REVERT, {}, data); + throw ExplicitlyTerminated(); + } + else if (_fun == "eth.getReturnDataSize"_yulstring) + return m_state.returndata.size(); + else if (_fun == "eth.returnDataCopy"_yulstring) + { + if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.returndata.size()) + throw ExplicitlyTerminated(); + if (accessMemory(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.calldata, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return {}; + } + else if (_fun == "eth.selfDestruct"_yulstring) + { + // TODO use readAddress to read address. + logTrace(eth::Instruction::SELFDESTRUCT, {}); + throw ExplicitlyTerminated(); + } + else if (_fun == "eth.getBlockTimestamp"_yulstring) + return m_state.timestamp; + + yulAssert(false, "Unknown builtin: " + _fun.str() + " (or implementation did not return)"); + + return 0; +} + +bool EWasmBuiltinInterpreter::accessMemory(u256 const& _offset, u256 const& _size) +{ + if (((_offset + _size) >= _offset) && ((_offset + _size + 0x1f) >= (_offset + _size))) + { + u256 newSize = (_offset + _size + 0x1f) & ~u256(0x1f); + m_state.msize = max(m_state.msize, newSize); + return _size <= 0xffff; + } + else + m_state.msize = u256(-1); + + return false; +} + +bytes EWasmBuiltinInterpreter::readMemory(uint64_t _offset, uint64_t _size) +{ + yulAssert(_size <= 0xffff, "Too large read."); + bytes data(size_t(_size), uint8_t(0)); + for (size_t i = 0; i < data.size(); ++i) + data[i] = m_state.memory[_offset + i]; + return data; +} + +uint64_t EWasmBuiltinInterpreter::readMemoryWord(uint64_t _offset) +{ + uint64_t r = 0; + for (size_t i = 0; i < 8; i++) + r |= uint64_t(m_state.memory[_offset + i]) << (i * 8); + return r; +} + +void EWasmBuiltinInterpreter::writeMemoryWord(uint64_t _offset, uint64_t _value) +{ + for (size_t i = 0; i < 8; i++) + m_state.memory[_offset + i] = uint8_t((_value >> (i * 8)) & 0xff); +} + +u256 EWasmBuiltinInterpreter::writeU256(uint64_t _offset, u256 _value, size_t _croppedTo) +{ + accessMemory(_offset, _croppedTo); + for (size_t i = 0; i < _croppedTo; i++) + { + m_state.memory[_offset + _croppedTo - 1 - i] = uint8_t(_value & 0xff); + _value >>= 8; + } + + return {}; +} + +u256 EWasmBuiltinInterpreter::readU256(uint64_t _offset, size_t _croppedTo) +{ + accessMemory(_offset, _croppedTo); + u256 value; + for (size_t i = 0; i < _croppedTo; i++) + value = (value << 8) | m_state.memory[_offset + i]; + + return value; +} + +void EWasmBuiltinInterpreter::logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments, bytes const& _data) +{ + logTrace(dev::eth::instructionInfo(_instruction).name, _arguments, _data); +} + +void EWasmBuiltinInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments, bytes const& _data) +{ + string message = _pseudoInstruction + "("; + for (size_t i = 0; i < _arguments.size(); ++i) + message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]); + message += ")"; + if (!_data.empty()) + message += " [" + toHex(_data) + "]"; + m_state.trace.emplace_back(std::move(message)); + if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize) + { + m_state.trace.emplace_back("Trace size limit reached."); + throw TraceLimitReached(); + } +} diff --git a/test/tools/yulInterpreter/EWasmBuiltinInterpreter.h b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.h new file mode 100644 index 000000000..e96cbbae2 --- /dev/null +++ b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.h @@ -0,0 +1,108 @@ +/* + 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 . +*/ +/** + * Yul interpreter module that evaluates EWasm builtins. + */ + +#pragma once + +#include + +#include + +#include + +namespace dev +{ +namespace eth +{ +enum class Instruction: uint8_t; +} +} + +namespace yul +{ +class YulString; +struct BuiltinFunctionForEVM; + +namespace test +{ + +struct InterpreterState; + +/** + * Interprets EWasm builtins based on the current state and logs instructions with + * side-effects. + * + * Since this is mainly meant to be used for differential fuzz testing, it is focused + * on a single contract only, does not do any gas counting and differs from the correct + * implementation in many ways: + * + * - If memory access to a "large" memory position is performed, a deterministic + * value is returned. Data that is stored in a "large" memory position is not + * retained. + * - The blockhash instruction returns a fixed value if the argument is in range. + * - Extcodesize returns a deterministic value depending on the address. + * - Extcodecopy copies a deterministic value depending on the address. + * - And many other things + * + * The main focus is that the generated execution trace is the same for equivalent executions + * and likely to be different for non-equivalent executions. + */ +class EWasmBuiltinInterpreter +{ +public: + explicit EWasmBuiltinInterpreter(InterpreterState& _state): + m_state(_state) + {} + /// Evaluate builtin function + dev::u256 evalBuiltin(YulString _fun, std::vector const& _arguments); + +private: + /// Checks if the memory access is not too large for the interpreter and adjusts + /// msize accordingly. + /// @returns false if the amount of bytes read is lager than 0xffff + bool accessMemory(dev::u256 const& _offset, dev::u256 const& _size = 32); + /// @returns the memory contents at the provided address. + /// Does not adjust msize, use @a accessMemory for that + dev::bytes readMemory(uint64_t _offset, uint64_t _size = 32); + /// @returns the memory contents at the provided address (little-endian). + /// Does not adjust msize, use @a accessMemory for that + uint64_t readMemoryWord(uint64_t _offset); + /// Writes a word to memory (little-endian) + /// Does not adjust msize, use @a accessMemory for that + void writeMemoryWord(uint64_t _offset, uint64_t _value); + + /// Helper for eth.* builtins. Writes to memory (big-endian) and always returns zero. + dev::u256 writeU256(uint64_t _offset, dev::u256 _value, size_t _croppedTo = 32); + dev::u256 writeU128(uint64_t _offset, dev::u256 _value) { return writeU256(_offset, std::move(_value), 16); } + dev::u256 writeAddress(uint64_t _offset, dev::u256 _value) { return writeU256(_offset, std::move(_value), 20); } + /// Helper for eth.* builtins. Reads from memory (big-endian) and returns the value; + dev::u256 readU256(uint64_t _offset, size_t _croppedTo = 32); + dev::u256 readU128(uint64_t _offset) { return readU256(_offset, 16); } + dev::u256 readAddress(uint64_t _offset) { return readU256(_offset, 20); } + + void logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + /// Appends a log to the trace representing an instruction or similar operation by string, + /// with arguments and auxiliary data (if nonempty). + void logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + + InterpreterState& m_state; +}; + +} +} diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 9f65709a5..d50c15b11 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -21,11 +21,13 @@ #include #include +#include #include #include #include #include +#include #include @@ -233,12 +235,21 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) evaluateArgs(_funCall.arguments); if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + { if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) { EVMInstructionInterpreter interpreter(m_state); setValue(interpreter.evalBuiltin(*fun, values())); return; } + } + else if (WasmDialect const* dialect = dynamic_cast(&m_dialect)) + if (dialect->builtin(_funCall.functionName.name)) + { + EWasmBuiltinInterpreter interpreter(m_state); + setValue(interpreter.evalBuiltin(_funCall.functionName.name, values())); + return; + } auto [functionScopes, fun] = findFunctionAndScope(_funCall.functionName.name);