Merge remote-tracking branch 'origin/develop' into develop_060

This commit is contained in:
chriseth 2019-11-26 16:19:35 +01:00
commit b0db64ff5b
65 changed files with 1942 additions and 203 deletions

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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 <string_literals>` and have the same convertibility restrictions.

View File

@ -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.

View File

@ -23,32 +23,59 @@
#include <libdevcore/Exceptions.h>
#include <libdevcore/Assertions.h>
#include <libdevcore/Keccak256.h>
#include <libdevcore/FixedHash.h>
#include <boost/algorithm/string.hpp>
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 + "\"";
}

View File

@ -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<T>& _vector, F const& _f)
_vector = std::move(modifiedVector);
}
namespace detail
{
template <typename T, typename F, std::size_t... I>
@ -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<typename Container, typename Compare>
bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare)
{

View File

@ -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<struct tag_invalidSymbol, char>;
using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
}

View File

@ -17,6 +17,7 @@
#include <libdevcore/IpfsHash.h>
#include <libdevcore/Assertions.h>
#include <libdevcore/Exceptions.h>
#include <libdevcore/picosha2.h>
#include <libdevcore/CommonData.h>
@ -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());

View File

@ -20,109 +20,41 @@
#pragma once
#include <functional>
#include <boost/variant/static_visitor.hpp>
namespace dev
{
/// Generic visitor used as follows:
/// std::visit(GenericVisitor<Class1, Class2>(
/// [](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<R>{},
* [](Class1& _c) { _c.f(); },
* [](Class2& _c) { _c.g(); }
* }, variant);
*/
template <class...>
struct GenericVisitor{};
template <typename...> struct VisitorFallback;
template <class Visitable, class... Others>
struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
{
using GenericVisitor<Others...>::operator ();
explicit GenericVisitor(
std::function<void(Visitable&)> _visitor,
std::function<void(Others&)>... _otherVisitors
):
GenericVisitor<Others...>(std::move(_otherVisitors)...),
m_visitor(std::move(_visitor))
{}
template <typename R>
struct VisitorFallback<R> { template<typename T> R operator()(T&&) const { return {}; } };
void operator()(Visitable& _v) const { m_visitor(_v); }
std::function<void(Visitable&)> m_visitor;
};
template <>
struct GenericVisitor<>: public boost::static_visitor<> {
void operator()() const {}
};
/// Generic visitor with fallback:
/// std::visit(GenericFallbackVisitor<Class1, Class2>(
/// [](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 <class...>
struct GenericFallbackVisitor{};
template <class Visitable, class... Others>
struct GenericFallbackVisitor<Visitable, Others...>: public GenericFallbackVisitor<Others...>
{
explicit GenericFallbackVisitor(
std::function<void(Visitable&)> _visitor,
std::function<void(Others&)>... _otherVisitors
):
GenericFallbackVisitor<Others...>(std::move(_otherVisitors)...),
m_visitor(std::move(_visitor))
{}
using GenericFallbackVisitor<Others...>::operator ();
void operator()(Visitable& _v) const { m_visitor(_v); }
std::function<void(Visitable&)> m_visitor;
};
template <>
struct GenericFallbackVisitor<>: public boost::static_visitor<> {
template <class T>
void operator()(T&) const { }
};
/// Generic visitor with fallback that can return a value:
/// std::visit(GenericFallbackReturnsVisitor<ReturnType, Class1, Class2>(
/// [](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 <class R, class...>
struct GenericFallbackReturnsVisitor{};
template <class R, class Visitable, class... Others>
struct GenericFallbackReturnsVisitor<R, Visitable, Others...>: public GenericFallbackReturnsVisitor<R, Others...>
{
explicit GenericFallbackReturnsVisitor(
std::function<R(Visitable&)> _visitor,
std::function<R(Others&)>... _otherVisitors
):
GenericFallbackReturnsVisitor<R, Others...>(std::move(_otherVisitors)...),
m_visitor(std::move(_visitor))
{}
using GenericFallbackReturnsVisitor<R, Others...>::operator ();
R operator()(Visitable& _v) const { return m_visitor(_v); }
std::function<R(Visitable&)> m_visitor;
};
template <class R>
struct GenericFallbackReturnsVisitor<R>: public boost::static_visitor<R> {
template <class T>
R operator()(T&) const { return {}; }
};
template<>
struct VisitorFallback<> { template<typename T> void operator()(T&&) const {} };
template <typename... Visitors> struct GenericVisitor: Visitors... { using Visitors::operator()...; };
template <typename... Visitors> GenericVisitor(Visitors...) -> GenericVisitor<Visitors...>;
}

View File

@ -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.");
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -33,5 +33,8 @@ struct OptimizerException: virtual AssemblyException {};
struct StackTooDeepException: virtual OptimizerException {};
struct ItemNotAvailableException: virtual OptimizerException {};
DEV_SIMPLE_EXCEPTION(InvalidDeposit);
DEV_SIMPLE_EXCEPTION(InvalidOpcode);
}
}

View File

@ -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
{

View File

@ -810,7 +810,7 @@ Token Scanner::scanHexString()
literal.complete();
advance(); // consume quote
return Token::StringLiteral;
return Token::HexStringLiteral;
}
// Parse for regex [:digit:]+(_[:digit:]+)*

View File

@ -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). */ \

View File

@ -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:

View File

@ -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;

View File

@ -23,6 +23,7 @@
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/Whiskers.h>
#include <libdevcore/StringUtils.h>
@ -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();
}
});

View File

@ -23,6 +23,8 @@
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/Whiskers.h>
#include <libdevcore/StringUtils.h>
@ -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();

View File

@ -1783,9 +1783,22 @@ ASTPointer<Expression> 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<Literal>(token, getLiteralAndAdvance());
m_scanner->next();
if (m_scanner->currentToken() == Token::Illegal)
fatalParserError(to_string(m_scanner->currentError()));
expression = nodeFactory.createNode<Literal>(token, make_shared<ASTString>(literal));
break;
}
case Token::Identifier:
nodeFactory.markEndPosition();
expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance());

View File

@ -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.");

View File

@ -48,8 +48,6 @@ struct Scope
};
using Identifier = std::variant<Variable, Label, Function>;
using Visitor = dev::GenericVisitor<Variable const, Label const, Function const>;
using NonconstVisitor = dev::GenericVisitor<Variable, Label, Function>;
bool registerVariable(YulString _name, YulType const& _type);
bool registerLabel(YulString _name);

View File

@ -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<Data>(name, asBytes(currentLiteral())));
advance();
}

View File

@ -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.");
}

View File

@ -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.");
}

View File

@ -190,7 +190,8 @@ void ControlFlowSimplifier::visit(Statement& _st)
void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
{
GenericFallbackReturnsVisitor<OptionalStatements, If, Switch> const visitor(
GenericVisitor visitor{
VisitorFallback<OptionalStatements>{},
[&](If& _ifStmt) -> OptionalStatements {
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction())
{
@ -215,8 +216,7 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
return {};
}
);
};
iterateReplacing(
_statements,
[&](Statement& _stmt) -> OptionalStatements

View File

@ -160,17 +160,19 @@ void InlineModifier::operator()(Block& _block)
std::optional<vector<Statement>> InlineModifier::tryInlineStatement(Statement& _statement)
{
// Only inline for expression statements, assignments and variable declarations.
Expression* e = std::visit(GenericFallbackReturnsVisitor<Expression*, ExpressionStatement, Assignment, VariableDeclaration>(
Expression* e = std::visit(GenericVisitor{
VisitorFallback<Expression*>{},
[](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*, FunctionCall&>(
FunctionCall* funCall = std::visit(GenericVisitor{
VisitorFallback<FunctionCall*>{},
[](FunctionCall& _e) { return &_e; }
), *e);
}, *e);
if (funCall && m_driver.shallInline(*funCall, m_currentFunction))
return performInline(_statement, *funCall);
}
@ -208,7 +210,8 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
Statement newBody = BodyCopier(m_nameDispenser, variableReplacements)(function->body);
newStatements += std::move(std::get<Block>(newBody).statements);
std::visit(GenericFallbackVisitor<Assignment, VariableDeclaration>{
std::visit(GenericVisitor{
VisitorFallback<>{},
[&](Assignment& _assignment)
{
for (size_t i = 0; i < _assignment.variableNames.size(); ++i)

View File

@ -71,7 +71,8 @@ void StructuralSimplifier::operator()(Block& _block)
void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
{
GenericFallbackReturnsVisitor<OptionalStatements, If, Switch, ForLoop> const visitor(
GenericVisitor visitor{
VisitorFallback<OptionalStatements>{},
[&](If& _ifStmt) -> OptionalStatements {
if (expressionAlwaysTrue(*_ifStmt.condition))
return {std::move(_ifStmt.body.statements)};
@ -89,7 +90,7 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
return {std::move(_forLoop.pre.statements)};
return {};
}
);
};
iterateReplacing(
_statements,

View File

@ -30,7 +30,8 @@ void VarDeclInitializer::operator()(Block& _block)
ASTModifier::operator()(_block);
using OptionalStatements = std::optional<vector<Statement>>;
GenericFallbackReturnsVisitor<OptionalStatements, VariableDeclaration> visitor{
GenericVisitor visitor{
VisitorFallback<OptionalStatements>{},
[](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); });
}

View File

@ -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

View File

@ -24,6 +24,7 @@
#include <test/libsolidity/SyntaxTest.h>
#include <test/libsolidity/SemanticTest.h>
#include <test/libsolidity/SMTCheckerJSONTest.h>
#include <test/libyul/EWasmTranslationTest.h>
#include <test/libyul/YulOptimizerTest.h>
#include <test/libyul/YulInterpreterTest.h>
#include <test/libyul/ObjectCompilerTest.h>
@ -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},

View File

@ -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"] }
}
}
}

View File

@ -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}}}

View File

@ -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"] }
}
}
}

View File

@ -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}}}

View File

@ -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"] }
}
}
}

View File

@ -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}}}

View File

@ -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"] }
}
}
}

View File

@ -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}}}

View File

@ -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"] }
}
}
}

View File

@ -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}}}

View File

@ -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));
}

View File

@ -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)

View File

@ -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.

View File

@ -0,0 +1,9 @@
contract test {
function f() public pure returns (bytes32) {
bytes32 escapeCharacters = hex"0000"
hex"deaf"
hex"feed";
return escapeCharacters;
}
}
// ----

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -0,0 +1,7 @@
contract test {
function f() public pure returns (bytes32) {
bytes32 escapeCharacters = "first" "second" "third";
return escapeCharacters;
}
}
// ----

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <test/libyul/EWasmTranslationTest.h>
#include <test/tools/yulInterpreter/Interpreter.h>
#include <test/Options.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/wasm/WasmDialect.h>
#include <libyul/backends/wasm/EVMToEWasmTranslator.h>
#include <libyul/AsmParser.h>
#include <libyul/AssemblyStack.h>
#include <libyul/AsmAnalysisInfo.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libdevcore/AnsiColorized.h>
#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string.hpp>
#include <fstream>
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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <test/TestCase.h>
#include <libyul/Object.h>
namespace langutil
{
class Scanner;
class Error;
using ErrorList = std::vector<std::shared_ptr<Error const>>;
}
namespace yul
{
namespace test
{
class EWasmTranslationTest: public dev::solidity::test::EVMVersionRestrictedTestCase
{
public:
static std::unique_ptr<TestCase> create(Config const& _config)
{
return std::unique_ptr<TestCase>(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<Object> m_object;
std::string m_obtainedResult;
};
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
{
mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20)
}
// ----
// Trace:
// Memory dump:
// 60: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
// Storage dump:

View File

@ -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

View File

@ -0,0 +1,5 @@
{}
// ----
// Trace:
// Memory dump:
// Storage dump:

View File

@ -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

View File

@ -1,6 +1,8 @@
set(sources
EVMInstructionInterpreter.h
EVMInstructionInterpreter.cpp
EWasmBuiltinInterpreter.h
EWasmBuiltinInterpreter.cpp
Interpreter.h
Interpreter.cpp
)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* Yul interpreter module that evaluates EWasm builtins.
*/
#include <test/tools/yulInterpreter/EWasmBuiltinInterpreter.h>
#include <test/tools/yulInterpreter/Interpreter.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AsmData.h>
#include <libevmasm/Instruction.h>
#include <libdevcore/Keccak256.h>
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<u256, uint8_t>& _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<boost::multiprecision::cpp_int_backend<512, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
u256 EWasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector<u256> const& _arguments)
{
vector<uint64_t> 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<u256> const& _arguments, bytes const& _data)
{
logTrace(dev::eth::instructionInfo(_instruction).name, _arguments, _data);
}
void EWasmBuiltinInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector<u256> 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();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* Yul interpreter module that evaluates EWasm builtins.
*/
#pragma once
#include <libyul/AsmDataForward.h>
#include <libdevcore/CommonData.h>
#include <vector>
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<dev::u256> 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<dev::u256> 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<dev::u256> const& _arguments = {}, dev::bytes const& _data = {});
InterpreterState& m_state;
};
}
}

View File

@ -21,11 +21,13 @@
#include <test/tools/yulInterpreter/Interpreter.h>
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
#include <test/tools/yulInterpreter/EWasmBuiltinInterpreter.h>
#include <libyul/AsmData.h>
#include <libyul/Dialect.h>
#include <libyul/Utilities.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/wasm/WasmDialect.h>
#include <liblangutil/Exceptions.h>
@ -233,12 +235,21 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
evaluateArgs(_funCall.arguments);
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&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<WasmDialect const*>(&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);