mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into develop_060
This commit is contained in:
commit
b0db64ff5b
@ -41,9 +41,11 @@ Compiler Features:
|
|||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
* Allow to obtain the selector of public or external library functions via a member ``.selector``.
|
* 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:
|
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:
|
Bugfixes:
|
||||||
|
@ -4,6 +4,25 @@
|
|||||||
Safe Remote Purchase
|
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;
|
pragma solidity >=0.4.22 <0.7.0;
|
||||||
@ -12,7 +31,7 @@ Safe Remote Purchase
|
|||||||
uint public value;
|
uint public value;
|
||||||
address payable public seller;
|
address payable public seller;
|
||||||
address payable public buyer;
|
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`
|
// The state variable has a default value of the first member, `State.created`
|
||||||
State public state;
|
State public state;
|
||||||
|
|
||||||
@ -57,6 +76,7 @@ Safe Remote Purchase
|
|||||||
event Aborted();
|
event Aborted();
|
||||||
event PurchaseConfirmed();
|
event PurchaseConfirmed();
|
||||||
event ItemReceived();
|
event ItemReceived();
|
||||||
|
event SellerRefunded();
|
||||||
|
|
||||||
/// Abort the purchase and reclaim the ether.
|
/// Abort the purchase and reclaim the ether.
|
||||||
/// Can only be called by the seller before
|
/// Can only be called by the seller before
|
||||||
@ -68,6 +88,10 @@ Safe Remote Purchase
|
|||||||
{
|
{
|
||||||
emit Aborted();
|
emit Aborted();
|
||||||
state = State.Inactive;
|
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);
|
seller.transfer(address(this).balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +121,24 @@ Safe Remote Purchase
|
|||||||
// It is important to change the state first because
|
// It is important to change the state first because
|
||||||
// otherwise, the contracts called using `send` below
|
// otherwise, the contracts called using `send` below
|
||||||
// can call in again here.
|
// can call in again here.
|
||||||
state = State.Inactive;
|
state = State.Release;
|
||||||
|
|
||||||
// NOTE: This actually allows both the buyer and the seller to
|
|
||||||
// block the refund - the withdraw pattern should be used.
|
|
||||||
|
|
||||||
buyer.transfer(value);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -464,7 +464,7 @@ a non-rational number).
|
|||||||
String Literals and Types
|
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.
|
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
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
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.
|
Hexadecimal literals behave like :ref:`string literals <string_literals>` and have the same convertibility restrictions.
|
||||||
|
|
||||||
|
@ -262,7 +262,15 @@ Contract Related
|
|||||||
the current contract, explicitly convertible to :ref:`address`
|
the current contract, explicitly convertible to :ref:`address`
|
||||||
|
|
||||||
``selfdestruct(address payable recipient)``:
|
``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.
|
Furthermore, all functions of the current contract are callable directly including the current function.
|
||||||
|
|
||||||
|
@ -23,32 +23,59 @@
|
|||||||
#include <libdevcore/Exceptions.h>
|
#include <libdevcore/Exceptions.h>
|
||||||
#include <libdevcore/Assertions.h>
|
#include <libdevcore/Assertions.h>
|
||||||
#include <libdevcore/Keccak256.h>
|
#include <libdevcore/Keccak256.h>
|
||||||
|
#include <libdevcore/FixedHash.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace dev;
|
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)
|
string dev::toHex(bytes const& _data, HexPrefix _prefix, HexCase _case)
|
||||||
{
|
{
|
||||||
std::ostringstream ret;
|
std::string ret(_data.size() * 2 + (_prefix == HexPrefix::Add ? 2 : 0), 0);
|
||||||
if (_prefix == HexPrefix::Add)
|
|
||||||
ret << "0x";
|
|
||||||
|
|
||||||
|
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;
|
int rix = _data.size() - 1;
|
||||||
for (uint8_t c: _data)
|
for (uint8_t c: _data)
|
||||||
{
|
{
|
||||||
// switch hex case every four hexchars
|
// switch hex case every four hexchars
|
||||||
auto hexcase = std::nouppercase;
|
if (_case == HexCase::Mixed)
|
||||||
if (_case == HexCase::Upper)
|
chars = (rix-- & 2) == 0 ? lowerHexChars : upperHexChars;
|
||||||
hexcase = std::uppercase;
|
|
||||||
else if (_case == HexCase::Mixed)
|
|
||||||
hexcase = (rix-- & 2) == 0 ? std::nouppercase : std::uppercase;
|
|
||||||
|
|
||||||
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)
|
int dev::fromHex(char _i, WhenError _throw)
|
||||||
@ -60,7 +87,7 @@ int dev::fromHex(char _i, WhenError _throw)
|
|||||||
if (_i >= 'A' && _i <= 'F')
|
if (_i >= 'A' && _i <= 'F')
|
||||||
return _i - 'A' + 10;
|
return _i - 'A' + 10;
|
||||||
if (_throw == WhenError::Throw)
|
if (_throw == WhenError::Throw)
|
||||||
BOOST_THROW_EXCEPTION(BadHexCharacter() << errinfo_invalidSymbol(_i));
|
assertThrow(false, BadHexCharacter, to_string(_i));
|
||||||
else
|
else
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -73,22 +100,18 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw)
|
|||||||
|
|
||||||
if (_s.size() % 2)
|
if (_s.size() % 2)
|
||||||
{
|
{
|
||||||
int h = fromHex(_s[s++], WhenError::DontThrow);
|
int h = fromHex(_s[s++], _throw);
|
||||||
if (h != -1)
|
if (h != -1)
|
||||||
ret.push_back(h);
|
ret.push_back(h);
|
||||||
else if (_throw == WhenError::Throw)
|
|
||||||
BOOST_THROW_EXCEPTION(BadHexCharacter());
|
|
||||||
else
|
else
|
||||||
return bytes();
|
return bytes();
|
||||||
}
|
}
|
||||||
for (unsigned i = s; i < _s.size(); i += 2)
|
for (unsigned i = s; i < _s.size(); i += 2)
|
||||||
{
|
{
|
||||||
int h = fromHex(_s[i], WhenError::DontThrow);
|
int h = fromHex(_s[i], _throw);
|
||||||
int l = fromHex(_s[i + 1], WhenError::DontThrow);
|
int l = fromHex(_s[i + 1], _throw);
|
||||||
if (h != -1 && l != -1)
|
if (h != -1 && l != -1)
|
||||||
ret.push_back((uint8_t)(h * 16 + l));
|
ret.push_back((uint8_t)(h * 16 + l));
|
||||||
else if (_throw == WhenError::Throw)
|
|
||||||
BOOST_THROW_EXCEPTION(BadHexCharacter());
|
|
||||||
else
|
else
|
||||||
return bytes();
|
return bytes();
|
||||||
}
|
}
|
||||||
@ -155,3 +178,15 @@ bool dev::isValidDecimal(string const& _string)
|
|||||||
return false;
|
return false;
|
||||||
return true;
|
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 + "\"";
|
||||||
|
}
|
||||||
|
@ -169,9 +169,12 @@ enum class HexCase
|
|||||||
Mixed = 2,
|
Mixed = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Convert a series of bytes to the corresponding string of hex duplets.
|
/// Convert a single byte to a string of hex characters (of length two),
|
||||||
/// @param _w specifies the width of the first of the elements. Defaults to two - enough to represent a byte.
|
/// optionally with uppercase hex letters.
|
||||||
/// @example toHex("A\x69") == "4169"
|
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);
|
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.
|
/// 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);
|
toBigEndian(_val, ret);
|
||||||
return 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
|
/// Convenience function for conversion of a u256 to hex
|
||||||
inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd)
|
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;
|
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.
|
/// Returns decimal representation for small numbers and hex for large numbers.
|
||||||
inline std::string formatNumber(bigint const& _value)
|
inline std::string formatNumber(bigint const& _value)
|
||||||
{
|
{
|
||||||
if (_value < 0)
|
if (_value < 0)
|
||||||
return "-" + formatNumber(-_value);
|
return "-" + formatNumber(-_value);
|
||||||
if (_value > 0x1000000)
|
if (_value > 0x1000000)
|
||||||
return toHex(toCompactBigEndian(_value), HexPrefix::Add);
|
return toHex(toCompactBigEndian(_value, 1), HexPrefix::Add);
|
||||||
else
|
else
|
||||||
return _value.str();
|
return _value.str();
|
||||||
}
|
}
|
||||||
@ -272,17 +276,11 @@ inline std::string formatNumber(bigint const& _value)
|
|||||||
inline std::string formatNumber(u256 const& _value)
|
inline std::string formatNumber(u256 const& _value)
|
||||||
{
|
{
|
||||||
if (_value > 0x1000000)
|
if (_value > 0x1000000)
|
||||||
return toHex(toCompactBigEndian(_value), HexPrefix::Add);
|
return toCompactHexWithPrefix(_value);
|
||||||
else
|
else
|
||||||
return _value.str();
|
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.
|
// Algorithms for string and string-like collections.
|
||||||
|
|
||||||
@ -337,7 +335,6 @@ void iterateReplacing(std::vector<T>& _vector, F const& _f)
|
|||||||
_vector = std::move(modifiedVector);
|
_vector = std::move(modifiedVector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
template <typename T, typename F, std::size_t... I>
|
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 isValidHex(std::string const& _string);
|
||||||
bool isValidDecimal(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>
|
template<typename Container, typename Compare>
|
||||||
bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare)
|
bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare)
|
||||||
{
|
{
|
||||||
|
@ -46,11 +46,11 @@ private:
|
|||||||
|
|
||||||
DEV_SIMPLE_EXCEPTION(InvalidAddress);
|
DEV_SIMPLE_EXCEPTION(InvalidAddress);
|
||||||
DEV_SIMPLE_EXCEPTION(BadHexCharacter);
|
DEV_SIMPLE_EXCEPTION(BadHexCharacter);
|
||||||
|
DEV_SIMPLE_EXCEPTION(BadHexCase);
|
||||||
DEV_SIMPLE_EXCEPTION(FileError);
|
DEV_SIMPLE_EXCEPTION(FileError);
|
||||||
DEV_SIMPLE_EXCEPTION(DataTooLong);
|
DEV_SIMPLE_EXCEPTION(DataTooLong);
|
||||||
|
|
||||||
// error information to be added to exceptions
|
// 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>;
|
using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include <libdevcore/IpfsHash.h>
|
#include <libdevcore/IpfsHash.h>
|
||||||
|
|
||||||
|
#include <libdevcore/Assertions.h>
|
||||||
#include <libdevcore/Exceptions.h>
|
#include <libdevcore/Exceptions.h>
|
||||||
#include <libdevcore/picosha2.h>
|
#include <libdevcore/picosha2.h>
|
||||||
#include <libdevcore/CommonData.h>
|
#include <libdevcore/CommonData.h>
|
||||||
@ -55,11 +56,7 @@ string base58Encode(bytes const& _data)
|
|||||||
|
|
||||||
bytes dev::ipfsHash(string _data)
|
bytes dev::ipfsHash(string _data)
|
||||||
{
|
{
|
||||||
if (_data.length() >= 1024 * 256)
|
assertThrow(_data.length() < 1024 * 256, DataTooLong, "IPFS hash for large (chunked) files not yet implemented.");
|
||||||
BOOST_THROW_EXCEPTION(
|
|
||||||
DataTooLong() <<
|
|
||||||
errinfo_comment("Ipfs hash for large (chunked) files not yet implemented.")
|
|
||||||
);
|
|
||||||
|
|
||||||
bytes lengthAsVarint = varintEncoding(_data.size());
|
bytes lengthAsVarint = varintEncoding(_data.size());
|
||||||
|
|
||||||
|
@ -20,109 +20,41 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <boost/variant/static_visitor.hpp>
|
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Generic visitor used as follows:
|
/**
|
||||||
/// std::visit(GenericVisitor<Class1, Class2>(
|
* Generic visitor used as follows:
|
||||||
/// [](Class1& _c) { _c.f(); },
|
* std::visit(GenericVisitor{
|
||||||
/// [](Class2& _c) { _c.g(); }
|
* [](Class1& _c) { _c.f(); },
|
||||||
/// ), variant);
|
* [](Class2& _c) { _c.g(); }
|
||||||
/// This one does not have a fallback and will fail at
|
* }, variant);
|
||||||
/// compile-time if you do not specify all variants.
|
* 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...>
|
template <typename...> struct VisitorFallback;
|
||||||
struct GenericVisitor{};
|
|
||||||
|
|
||||||
template <class Visitable, class... Others>
|
template <typename R>
|
||||||
struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
|
struct VisitorFallback<R> { template<typename T> R operator()(T&&) const { return {}; } };
|
||||||
{
|
|
||||||
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))
|
|
||||||
{}
|
|
||||||
|
|
||||||
void operator()(Visitable& _v) const { m_visitor(_v); }
|
template<>
|
||||||
|
struct VisitorFallback<> { template<typename T> void operator()(T&&) const {} };
|
||||||
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 <typename... Visitors> struct GenericVisitor: Visitors... { using Visitors::operator()...; };
|
||||||
|
template <typename... Visitors> GenericVisitor(Visitors...) -> GenericVisitor<Visitors...>;
|
||||||
}
|
}
|
||||||
|
@ -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())));
|
collection.append(createJsonValue("PUSH data", i.location().start, i.location().end, toStringInHex(i.data())));
|
||||||
break;
|
break;
|
||||||
default:
|
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);
|
ret.bytecode.push_back((uint8_t)Instruction::JUMPDEST);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
assertThrow(false, InvalidOpcode, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
int AssemblyItem::arguments() const
|
int AssemblyItem::arguments() const
|
||||||
@ -212,7 +212,7 @@ string AssemblyItem::toAssemblyText() const
|
|||||||
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
assertThrow(false, InvalidOpcode, "");
|
||||||
}
|
}
|
||||||
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
|
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
|
||||||
{
|
{
|
||||||
@ -277,7 +277,7 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item)
|
|||||||
_out << " ???";
|
_out << " ???";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
assertThrow(false, InvalidOpcode, "");
|
||||||
}
|
}
|
||||||
return _out;
|
return _out;
|
||||||
}
|
}
|
||||||
|
@ -158,11 +158,10 @@ AssemblyItems CSECodeGenerator::generateCode(
|
|||||||
for (auto id: {p.first, p.second})
|
for (auto id: {p.first, p.second})
|
||||||
if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber)
|
if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber)
|
||||||
{
|
{
|
||||||
if (seqNr < _initialSequenceNumber)
|
// Invalid sequenced operation.
|
||||||
// Invalid sequenced operation.
|
// @todo quick fix for now. Proper fix needs to choose representative with higher
|
||||||
// @todo quick fix for now. Proper fix needs to choose representative with higher
|
// sequence number during dependency analysis.
|
||||||
// sequence number during dependency analysis.
|
assertThrow(seqNr >= _initialSequenceNumber, StackTooDeepException, "");
|
||||||
BOOST_THROW_EXCEPTION(StackTooDeepException());
|
|
||||||
sequencedExpressions.insert(make_pair(seqNr, id));
|
sequencedExpressions.insert(make_pair(seqNr, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,12 +221,9 @@ void CSECodeGenerator::addDependencies(Id _c)
|
|||||||
return; // we already computed the dependencies for _c
|
return; // we already computed the dependencies for _c
|
||||||
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
|
ExpressionClasses::Expression expr = m_expressionClasses.representative(_c);
|
||||||
assertThrow(expr.item, OptimizerException, "");
|
assertThrow(expr.item, OptimizerException, "");
|
||||||
if (expr.item->type() == UndefinedItem)
|
// If this exception happens, we need to find a different way to generate the
|
||||||
BOOST_THROW_EXCEPTION(
|
// compound expression.
|
||||||
// If this exception happens, we need to find a different way to generate the
|
assertThrow(expr.item->type() != UndefinedItem, ItemNotAvailableException, "Undefined item requested but not available.");
|
||||||
// compound expression.
|
|
||||||
ItemNotAvailableException() << errinfo_comment("Undefined item requested but not available.")
|
|
||||||
);
|
|
||||||
for (Id argument: expr.arguments)
|
for (Id argument: expr.arguments)
|
||||||
{
|
{
|
||||||
addDependencies(argument);
|
addDependencies(argument);
|
||||||
|
@ -33,5 +33,8 @@ struct OptimizerException: virtual AssemblyException {};
|
|||||||
struct StackTooDeepException: virtual OptimizerException {};
|
struct StackTooDeepException: virtual OptimizerException {};
|
||||||
struct ItemNotAvailableException: virtual OptimizerException {};
|
struct ItemNotAvailableException: virtual OptimizerException {};
|
||||||
|
|
||||||
|
DEV_SIMPLE_EXCEPTION(InvalidDeposit);
|
||||||
|
DEV_SIMPLE_EXCEPTION(InvalidOpcode);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,6 @@ namespace dev
|
|||||||
namespace eth
|
namespace eth
|
||||||
{
|
{
|
||||||
|
|
||||||
DEV_SIMPLE_EXCEPTION(InvalidDeposit);
|
|
||||||
DEV_SIMPLE_EXCEPTION(InvalidOpcode);
|
|
||||||
|
|
||||||
/// Virtual machine bytecode instruction.
|
/// Virtual machine bytecode instruction.
|
||||||
enum class Instruction: uint8_t
|
enum class Instruction: uint8_t
|
||||||
{
|
{
|
||||||
|
@ -810,7 +810,7 @@ Token Scanner::scanHexString()
|
|||||||
|
|
||||||
literal.complete();
|
literal.complete();
|
||||||
advance(); // consume quote
|
advance(); // consume quote
|
||||||
return Token::StringLiteral;
|
return Token::HexStringLiteral;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse for regex [:digit:]+(_[:digit:]+)*
|
// Parse for regex [:digit:]+(_[:digit:]+)*
|
||||||
|
@ -226,6 +226,7 @@ namespace langutil
|
|||||||
K(FalseLiteral, "false", 0) \
|
K(FalseLiteral, "false", 0) \
|
||||||
T(Number, nullptr, 0) \
|
T(Number, nullptr, 0) \
|
||||||
T(StringLiteral, nullptr, 0) \
|
T(StringLiteral, nullptr, 0) \
|
||||||
|
T(HexStringLiteral, nullptr, 0) \
|
||||||
T(CommentLiteral, nullptr, 0) \
|
T(CommentLiteral, nullptr, 0) \
|
||||||
\
|
\
|
||||||
/* Identifiers (not keywords or future reserved words). */ \
|
/* Identifiers (not keywords or future reserved words). */ \
|
||||||
|
@ -851,6 +851,7 @@ string ASTJsonConverter::literalTokenKind(Token _token)
|
|||||||
case dev::solidity::Token::Number:
|
case dev::solidity::Token::Number:
|
||||||
return "number";
|
return "number";
|
||||||
case dev::solidity::Token::StringLiteral:
|
case dev::solidity::Token::StringLiteral:
|
||||||
|
case dev::solidity::Token::HexStringLiteral:
|
||||||
return "string";
|
return "string";
|
||||||
case dev::solidity::Token::TrueLiteral:
|
case dev::solidity::Token::TrueLiteral:
|
||||||
case dev::solidity::Token::FalseLiteral:
|
case dev::solidity::Token::FalseLiteral:
|
||||||
|
@ -347,6 +347,7 @@ TypePointer TypeProvider::forLiteral(Literal const& _literal)
|
|||||||
case Token::Number:
|
case Token::Number:
|
||||||
return rationalNumber(_literal);
|
return rationalNumber(_literal);
|
||||||
case Token::StringLiteral:
|
case Token::StringLiteral:
|
||||||
|
case Token::HexStringLiteral:
|
||||||
return stringLiteral(_literal.value());
|
return stringLiteral(_literal.value());
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <libsolidity/codegen/ABIFunctions.h>
|
#include <libsolidity/codegen/ABIFunctions.h>
|
||||||
|
|
||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
|
#include <libdevcore/CommonData.h>
|
||||||
#include <libdevcore/Whiskers.h>
|
#include <libdevcore/Whiskers.h>
|
||||||
#include <libdevcore/StringUtils.h>
|
#include <libdevcore/StringUtils.h>
|
||||||
|
|
||||||
@ -975,7 +976,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
|
|||||||
for (size_t i = 0; i < words; ++i)
|
for (size_t i = 0; i < words; ++i)
|
||||||
{
|
{
|
||||||
wordParams[i]["offset"] = to_string(i * 32);
|
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);
|
templ("word", wordParams);
|
||||||
return templ.render();
|
return templ.render();
|
||||||
@ -990,7 +991,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
|
|||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex());
|
templ("wordValue", formatAsStringOrNumber(value));
|
||||||
return templ.render();
|
return templ.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
|
|
||||||
|
#include <libdevcore/CommonData.h>
|
||||||
#include <libdevcore/Whiskers.h>
|
#include <libdevcore/Whiskers.h>
|
||||||
#include <libdevcore/StringUtils.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)
|
for (size_t i = 0; i < words; ++i)
|
||||||
{
|
{
|
||||||
wordParams[i]["offset"] = to_string(32 + i * 32);
|
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);
|
templ("word", wordParams);
|
||||||
return templ.render();
|
return templ.render();
|
||||||
|
@ -1783,9 +1783,22 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Token::StringLiteral:
|
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();
|
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;
|
break;
|
||||||
|
}
|
||||||
case Token::Identifier:
|
case Token::Identifier:
|
||||||
nodeFactory.markEndPosition();
|
nodeFactory.markEndPosition();
|
||||||
expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance());
|
expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance());
|
||||||
|
@ -130,7 +130,7 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier)
|
|||||||
solAssert(!_identifier.name.empty(), "");
|
solAssert(!_identifier.name.empty(), "");
|
||||||
size_t numErrorsBefore = m_errorReporter.errors().size();
|
size_t numErrorsBefore = m_errorReporter.errors().size();
|
||||||
bool success = true;
|
bool success = true;
|
||||||
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
|
if (m_currentScope->lookup(_identifier.name, GenericVisitor{
|
||||||
[&](Scope::Variable const& _var)
|
[&](Scope::Variable const& _var)
|
||||||
{
|
{
|
||||||
if (!m_activeVariables.count(&_var))
|
if (!m_activeVariables.count(&_var))
|
||||||
@ -155,7 +155,7 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier)
|
|||||||
);
|
);
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
)))
|
}))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -304,7 +304,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
if (f->literalArguments)
|
if (f->literalArguments)
|
||||||
needsLiteralArguments = true;
|
needsLiteralArguments = true;
|
||||||
}
|
}
|
||||||
else if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
|
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
||||||
[&](Scope::Variable const&)
|
[&](Scope::Variable const&)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
@ -327,7 +327,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
parameters = _fun.arguments.size();
|
parameters = _fun.arguments.size();
|
||||||
returns = _fun.returns.size();
|
returns = _fun.returns.size();
|
||||||
}
|
}
|
||||||
)))
|
}))
|
||||||
{
|
{
|
||||||
if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location))
|
if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location))
|
||||||
m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
|
m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
|
||||||
|
@ -48,8 +48,6 @@ struct Scope
|
|||||||
};
|
};
|
||||||
|
|
||||||
using Identifier = std::variant<Variable, Label, Function>;
|
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 registerVariable(YulString _name, YulType const& _type);
|
||||||
bool registerLabel(YulString _name);
|
bool registerLabel(YulString _name);
|
||||||
|
@ -120,7 +120,10 @@ void ObjectParser::parseData(Object& _containingObject)
|
|||||||
|
|
||||||
YulString name = parseUniqueName(&_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())));
|
addNamedSubObject(_containingObject, name, make_shared<Data>(name, asBytes(currentLiteral())));
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
|
@ -207,11 +207,11 @@ void CodeGenerator::assemble(
|
|||||||
}
|
}
|
||||||
catch (StackTooDeepError const& _e)
|
catch (StackTooDeepError const& _e)
|
||||||
{
|
{
|
||||||
BOOST_THROW_EXCEPTION(
|
solAssert(
|
||||||
InternalCompilerError() << errinfo_comment(
|
false,
|
||||||
"Stack too deep when compiling inline assembly" +
|
"Stack too deep when compiling inline assembly" +
|
||||||
(_e.comment() ? ": " + *_e.comment() : ".")
|
(_e.comment() ? ": " + *_e.comment() : ".")
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
solAssert(transform.stackErrors().empty(), "Stack errors present but not thrown.");
|
solAssert(transform.stackErrors().empty(), "Stack errors present but not thrown.");
|
||||||
}
|
}
|
||||||
|
@ -82,14 +82,14 @@ void VariableReferenceCounter::operator()(Block const& _block)
|
|||||||
|
|
||||||
void VariableReferenceCounter::increaseRefIfFound(YulString _variableName)
|
void VariableReferenceCounter::increaseRefIfFound(YulString _variableName)
|
||||||
{
|
{
|
||||||
m_scope->lookup(_variableName, Scope::Visitor(
|
m_scope->lookup(_variableName, GenericVisitor{
|
||||||
[=](Scope::Variable const& _var)
|
[=](Scope::Variable const& _var)
|
||||||
{
|
{
|
||||||
++m_context.variableReferences[&_var];
|
++m_context.variableReferences[&_var];
|
||||||
},
|
},
|
||||||
[=](Scope::Label const&) { },
|
[=](Scope::Label const&) { },
|
||||||
[=](Scope::Function const&) { }
|
[=](Scope::Function const&) { }
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeTransform::CodeTransform(
|
CodeTransform::CodeTransform(
|
||||||
@ -283,11 +283,11 @@ void CodeTransform::operator()(FunctionCall const& _call)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Scope::Function* function = nullptr;
|
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::Variable&) { solAssert(false, "Expected function name."); },
|
||||||
[=](Scope::Label&) { solAssert(false, "Expected function name."); },
|
[=](Scope::Label&) { solAssert(false, "Expected function name."); },
|
||||||
[&](Scope::Function& _function) { function = &_function; }
|
[&](Scope::Function& _function) { function = &_function; }
|
||||||
)), "Function name not found.");
|
}), "Function name not found.");
|
||||||
solAssert(function, "");
|
solAssert(function, "");
|
||||||
solAssert(function->arguments.size() == _call.arguments.size(), "");
|
solAssert(function->arguments.size() == _call.arguments.size(), "");
|
||||||
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
|
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
|
||||||
@ -310,7 +310,7 @@ void CodeTransform::operator()(Identifier const& _identifier)
|
|||||||
m_assembly.setSourceLocation(_identifier.location);
|
m_assembly.setSourceLocation(_identifier.location);
|
||||||
// First search internals, then externals.
|
// First search internals, then externals.
|
||||||
solAssert(m_scope, "");
|
solAssert(m_scope, "");
|
||||||
if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
|
if (m_scope->lookup(_identifier.name, GenericVisitor{
|
||||||
[=](Scope::Variable& _var)
|
[=](Scope::Variable& _var)
|
||||||
{
|
{
|
||||||
// TODO: opportunity for optimization: Do not DUP if this is the last reference
|
// 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.");
|
solAssert(false, "Function not removed during desugaring.");
|
||||||
}
|
}
|
||||||
)))
|
}))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -646,14 +646,14 @@ void CodeTransform::operator()(Block const& _block)
|
|||||||
AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
|
AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1);
|
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::Variable&) { solAssert(false, "Expected label"); },
|
||||||
[&](Scope::Label& _label)
|
[&](Scope::Label& _label)
|
||||||
{
|
{
|
||||||
label = labelID(_label);
|
label = labelID(_label);
|
||||||
},
|
},
|
||||||
[=](Scope::Function&) { solAssert(false, "Expected label"); }
|
[=](Scope::Function&) { solAssert(false, "Expected label"); }
|
||||||
)))
|
}))
|
||||||
{
|
{
|
||||||
solAssert(false, "Identifier not found.");
|
solAssert(false, "Identifier not found.");
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,8 @@ void ControlFlowSimplifier::visit(Statement& _st)
|
|||||||
|
|
||||||
void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
||||||
{
|
{
|
||||||
GenericFallbackReturnsVisitor<OptionalStatements, If, Switch> const visitor(
|
GenericVisitor visitor{
|
||||||
|
VisitorFallback<OptionalStatements>{},
|
||||||
[&](If& _ifStmt) -> OptionalStatements {
|
[&](If& _ifStmt) -> OptionalStatements {
|
||||||
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction())
|
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction())
|
||||||
{
|
{
|
||||||
@ -215,8 +216,7 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
|||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
);
|
};
|
||||||
|
|
||||||
iterateReplacing(
|
iterateReplacing(
|
||||||
_statements,
|
_statements,
|
||||||
[&](Statement& _stmt) -> OptionalStatements
|
[&](Statement& _stmt) -> OptionalStatements
|
||||||
|
@ -160,17 +160,19 @@ void InlineModifier::operator()(Block& _block)
|
|||||||
std::optional<vector<Statement>> InlineModifier::tryInlineStatement(Statement& _statement)
|
std::optional<vector<Statement>> InlineModifier::tryInlineStatement(Statement& _statement)
|
||||||
{
|
{
|
||||||
// Only inline for expression statements, assignments and variable declarations.
|
// 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; },
|
[](ExpressionStatement& _s) { return &_s.expression; },
|
||||||
[](Assignment& _s) { return _s.value.get(); },
|
[](Assignment& _s) { return _s.value.get(); },
|
||||||
[](VariableDeclaration& _s) { return _s.value.get(); }
|
[](VariableDeclaration& _s) { return _s.value.get(); }
|
||||||
), _statement);
|
}, _statement);
|
||||||
if (e)
|
if (e)
|
||||||
{
|
{
|
||||||
// Only inline direct function calls.
|
// Only inline direct function calls.
|
||||||
FunctionCall* funCall = std::visit(GenericFallbackReturnsVisitor<FunctionCall*, FunctionCall&>(
|
FunctionCall* funCall = std::visit(GenericVisitor{
|
||||||
|
VisitorFallback<FunctionCall*>{},
|
||||||
[](FunctionCall& _e) { return &_e; }
|
[](FunctionCall& _e) { return &_e; }
|
||||||
), *e);
|
}, *e);
|
||||||
if (funCall && m_driver.shallInline(*funCall, m_currentFunction))
|
if (funCall && m_driver.shallInline(*funCall, m_currentFunction))
|
||||||
return performInline(_statement, *funCall);
|
return performInline(_statement, *funCall);
|
||||||
}
|
}
|
||||||
@ -208,7 +210,8 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
|
|||||||
Statement newBody = BodyCopier(m_nameDispenser, variableReplacements)(function->body);
|
Statement newBody = BodyCopier(m_nameDispenser, variableReplacements)(function->body);
|
||||||
newStatements += std::move(std::get<Block>(newBody).statements);
|
newStatements += std::move(std::get<Block>(newBody).statements);
|
||||||
|
|
||||||
std::visit(GenericFallbackVisitor<Assignment, VariableDeclaration>{
|
std::visit(GenericVisitor{
|
||||||
|
VisitorFallback<>{},
|
||||||
[&](Assignment& _assignment)
|
[&](Assignment& _assignment)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < _assignment.variableNames.size(); ++i)
|
for (size_t i = 0; i < _assignment.variableNames.size(); ++i)
|
||||||
|
@ -71,7 +71,8 @@ void StructuralSimplifier::operator()(Block& _block)
|
|||||||
|
|
||||||
void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
||||||
{
|
{
|
||||||
GenericFallbackReturnsVisitor<OptionalStatements, If, Switch, ForLoop> const visitor(
|
GenericVisitor visitor{
|
||||||
|
VisitorFallback<OptionalStatements>{},
|
||||||
[&](If& _ifStmt) -> OptionalStatements {
|
[&](If& _ifStmt) -> OptionalStatements {
|
||||||
if (expressionAlwaysTrue(*_ifStmt.condition))
|
if (expressionAlwaysTrue(*_ifStmt.condition))
|
||||||
return {std::move(_ifStmt.body.statements)};
|
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 {std::move(_forLoop.pre.statements)};
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
);
|
};
|
||||||
|
|
||||||
iterateReplacing(
|
iterateReplacing(
|
||||||
_statements,
|
_statements,
|
||||||
|
@ -30,7 +30,8 @@ void VarDeclInitializer::operator()(Block& _block)
|
|||||||
ASTModifier::operator()(_block);
|
ASTModifier::operator()(_block);
|
||||||
|
|
||||||
using OptionalStatements = std::optional<vector<Statement>>;
|
using OptionalStatements = std::optional<vector<Statement>>;
|
||||||
GenericFallbackReturnsVisitor<OptionalStatements, VariableDeclaration> visitor{
|
GenericVisitor visitor{
|
||||||
|
VisitorFallback<OptionalStatements>{},
|
||||||
[](VariableDeclaration& _varDecl) -> OptionalStatements
|
[](VariableDeclaration& _varDecl) -> OptionalStatements
|
||||||
{
|
{
|
||||||
if (_varDecl.value)
|
if (_varDecl.value)
|
||||||
@ -51,5 +52,6 @@ void VarDeclInitializer::operator()(Block& _block)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
iterateReplacing(_block.statements, [&](auto&& _statement) { return std::visit(visitor, _statement); });
|
iterateReplacing(_block.statements, [&](auto&& _statement) { return std::visit(visitor, _statement); });
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,8 @@ set(libyul_sources
|
|||||||
libyul/Common.cpp
|
libyul/Common.cpp
|
||||||
libyul/Common.h
|
libyul/Common.h
|
||||||
libyul/CompilabilityChecker.cpp
|
libyul/CompilabilityChecker.cpp
|
||||||
|
libyul/EWasmTranslationTest.cpp
|
||||||
|
libyul/EWasmTranslationTest.h
|
||||||
libyul/FunctionSideEffects.cpp
|
libyul/FunctionSideEffects.cpp
|
||||||
libyul/FunctionSideEffects.h
|
libyul/FunctionSideEffects.h
|
||||||
libyul/Inliner.cpp
|
libyul/Inliner.cpp
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <test/libsolidity/SyntaxTest.h>
|
#include <test/libsolidity/SyntaxTest.h>
|
||||||
#include <test/libsolidity/SemanticTest.h>
|
#include <test/libsolidity/SemanticTest.h>
|
||||||
#include <test/libsolidity/SMTCheckerJSONTest.h>
|
#include <test/libsolidity/SMTCheckerJSONTest.h>
|
||||||
|
#include <test/libyul/EWasmTranslationTest.h>
|
||||||
#include <test/libyul/YulOptimizerTest.h>
|
#include <test/libyul/YulOptimizerTest.h>
|
||||||
#include <test/libyul/YulInterpreterTest.h>
|
#include <test/libyul/YulInterpreterTest.h>
|
||||||
#include <test/libyul/ObjectCompilerTest.h>
|
#include <test/libyul/ObjectCompilerTest.h>
|
||||||
@ -54,6 +55,7 @@ struct Testsuite
|
|||||||
Testsuite const g_interactiveTestsuites[] = {
|
Testsuite const g_interactiveTestsuites[] = {
|
||||||
/*
|
/*
|
||||||
Title Path Subpath SMT NeedsVM Creator function */
|
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 Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
|
||||||
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
|
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
|
||||||
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},
|
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},
|
||||||
|
17
test/cmdlineTests/yul_string_format_ascii/input.json
Normal file
17
test/cmdlineTests/yul_string_format_ascii/input.json
Normal 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"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
test/cmdlineTests/yul_string_format_ascii/output.json
Normal file
161
test/cmdlineTests/yul_string_format_ascii/output.json
Normal 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}}}
|
17
test/cmdlineTests/yul_string_format_ascii_bytes32/input.json
Normal file
17
test/cmdlineTests/yul_string_format_ascii_bytes32/input.json
Normal 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"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
test/cmdlineTests/yul_string_format_ascii_bytes32/output.json
Normal file
114
test/cmdlineTests/yul_string_format_ascii_bytes32/output.json
Normal 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}}}
|
@ -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"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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}}}
|
17
test/cmdlineTests/yul_string_format_ascii_long/input.json
Normal file
17
test/cmdlineTests/yul_string_format_ascii_long/input.json
Normal 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"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
169
test/cmdlineTests/yul_string_format_ascii_long/output.json
Normal file
169
test/cmdlineTests/yul_string_format_ascii_long/output.json
Normal 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}}}
|
17
test/cmdlineTests/yul_string_format_hex/input.json
Normal file
17
test/cmdlineTests/yul_string_format_hex/input.json
Normal 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"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
test/cmdlineTests/yul_string_format_hex/output.json
Normal file
138
test/cmdlineTests/yul_string_format_hex/output.json
Normal 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}}}
|
@ -505,7 +505,7 @@ BOOST_AUTO_TEST_CASE(valid_hex_literal)
|
|||||||
{
|
{
|
||||||
Scanner scanner(CharStream("{ hex\"00112233FF\"", ""));
|
Scanner scanner(CharStream("{ hex\"00112233FF\"", ""));
|
||||||
BOOST_CHECK_EQUAL(scanner.currentToken(), Token::LBrace);
|
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));
|
BOOST_CHECK_EQUAL(scanner.currentLiteral(), std::string("\x00\x11\x22\x33\xFF", 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
test/libsolidity/semanticTests/viaYul/string_format.sol
Normal file
13
test/libsolidity/semanticTests/viaYul/string_format.sol
Normal 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)
|
@ -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.
|
@ -0,0 +1,9 @@
|
|||||||
|
contract test {
|
||||||
|
function f() public pure returns (bytes32) {
|
||||||
|
bytes32 escapeCharacters = hex"0000"
|
||||||
|
hex"deaf"
|
||||||
|
hex"feed";
|
||||||
|
return escapeCharacters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -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'
|
@ -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'
|
@ -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'
|
@ -0,0 +1,7 @@
|
|||||||
|
contract test {
|
||||||
|
function f() public pure returns (bytes32) {
|
||||||
|
bytes32 escapeCharacters = "first" "second" "third";
|
||||||
|
return escapeCharacters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
156
test/libyul/EWasmTranslationTest.cpp
Normal file
156
test/libyul/EWasmTranslationTest.cpp
Normal 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);
|
||||||
|
}
|
65
test/libyul/EWasmTranslationTest.h
Normal file
65
test/libyul/EWasmTranslationTest.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
20
test/libyul/ewasmTranslationTests/datacopy.yul
Normal file
20
test/libyul/ewasmTranslationTests/datacopy.yul
Normal 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
|
16
test/libyul/ewasmTranslationTests/dataoffset.yul
Normal file
16
test/libyul/ewasmTranslationTests/dataoffset.yul
Normal 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
|
16
test/libyul/ewasmTranslationTests/datasize.yul
Normal file
16
test/libyul/ewasmTranslationTests/datasize.yul
Normal 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
|
14
test/libyul/ewasmTranslationTests/mstore_mload.yul
Normal file
14
test/libyul/ewasmTranslationTests/mstore_mload.yul
Normal 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
|
17
test/libyul/ewasmTranslationTests/shl.yul
Normal file
17
test/libyul/ewasmTranslationTests/shl.yul
Normal 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
|
8
test/libyul/ewasmTranslationTests/simple_mstore.yul
Normal file
8
test/libyul/ewasmTranslationTests/simple_mstore.yul
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20)
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Trace:
|
||||||
|
// Memory dump:
|
||||||
|
// 60: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
|
||||||
|
// Storage dump:
|
12
test/libyul/ewasmTranslationTests/simple_sstore.yul
Normal file
12
test/libyul/ewasmTranslationTests/simple_sstore.yul
Normal 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
|
5
test/libyul/ewasmTranslationTests/smoke.yul
Normal file
5
test/libyul/ewasmTranslationTests/smoke.yul
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{}
|
||||||
|
// ----
|
||||||
|
// Trace:
|
||||||
|
// Memory dump:
|
||||||
|
// Storage dump:
|
@ -31,6 +31,7 @@ add_executable(isoltest
|
|||||||
../libsolidity/ASTJSONTest.cpp
|
../libsolidity/ASTJSONTest.cpp
|
||||||
../libsolidity/SMTCheckerJSONTest.cpp
|
../libsolidity/SMTCheckerJSONTest.cpp
|
||||||
../libyul/Common.cpp
|
../libyul/Common.cpp
|
||||||
|
../libyul/EWasmTranslationTest.cpp
|
||||||
../libyul/FunctionSideEffects.cpp
|
../libyul/FunctionSideEffects.cpp
|
||||||
../libyul/ObjectCompilerTest.cpp
|
../libyul/ObjectCompilerTest.cpp
|
||||||
../libyul/YulOptimizerTest.cpp
|
../libyul/YulOptimizerTest.cpp
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
set(sources
|
set(sources
|
||||||
EVMInstructionInterpreter.h
|
EVMInstructionInterpreter.h
|
||||||
EVMInstructionInterpreter.cpp
|
EVMInstructionInterpreter.cpp
|
||||||
|
EWasmBuiltinInterpreter.h
|
||||||
|
EWasmBuiltinInterpreter.cpp
|
||||||
Interpreter.h
|
Interpreter.h
|
||||||
Interpreter.cpp
|
Interpreter.cpp
|
||||||
)
|
)
|
||||||
|
376
test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp
Normal file
376
test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
108
test/tools/yulInterpreter/EWasmBuiltinInterpreter.h
Normal file
108
test/tools/yulInterpreter/EWasmBuiltinInterpreter.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -21,11 +21,13 @@
|
|||||||
#include <test/tools/yulInterpreter/Interpreter.h>
|
#include <test/tools/yulInterpreter/Interpreter.h>
|
||||||
|
|
||||||
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
|
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
|
||||||
|
#include <test/tools/yulInterpreter/EWasmBuiltinInterpreter.h>
|
||||||
|
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <libyul/Dialect.h>
|
#include <libyul/Dialect.h>
|
||||||
#include <libyul/Utilities.h>
|
#include <libyul/Utilities.h>
|
||||||
#include <libyul/backends/evm/EVMDialect.h>
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
#include <libyul/backends/wasm/WasmDialect.h>
|
||||||
|
|
||||||
#include <liblangutil/Exceptions.h>
|
#include <liblangutil/Exceptions.h>
|
||||||
|
|
||||||
@ -233,12 +235,21 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
|
|||||||
evaluateArgs(_funCall.arguments);
|
evaluateArgs(_funCall.arguments);
|
||||||
|
|
||||||
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
||||||
|
{
|
||||||
if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
|
if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
|
||||||
{
|
{
|
||||||
EVMInstructionInterpreter interpreter(m_state);
|
EVMInstructionInterpreter interpreter(m_state);
|
||||||
setValue(interpreter.evalBuiltin(*fun, values()));
|
setValue(interpreter.evalBuiltin(*fun, values()));
|
||||||
return;
|
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);
|
auto [functionScopes, fun] = findFunctionAndScope(_funCall.functionName.name);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user