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

This commit is contained in:
chriseth 2022-04-13 17:08:27 +02:00
commit a433511128
186 changed files with 3081 additions and 976 deletions

View File

@ -246,11 +246,11 @@ defaults:
- steps_install_dependencies_osx: &steps_install_dependencies_osx
steps:
- restore_cache:
keys:
- dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
- attach_workspace:
at: .
# FIXME: We used to cache dependencies on macOS but now it takes longer than just installing
# them each time. See https://github.com/ethereum/solidity/issues/12925.
- run:
name: Install build dependencies
command: ./.circleci/osx_install_dependencies.sh
# --------------------------------------------------------------------------
# Base Image Templates
@ -582,6 +582,12 @@ defaults:
binary_type: native
nodejs_version: '16'
resource_class: medium
- job_native_test_ext_brink: &job_native_test_ext_brink
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_brink
project: brink
binary_type: native
nodejs_version: '16'
- job_ems_test_ext_colony: &job_ems_test_ext_colony
<<: *workflow_emscripten
name: t_ems_test_ext_colony
@ -900,23 +906,9 @@ jobs:
MAKEFLAGS: -j10
steps:
- checkout
- restore_cache:
keys:
- dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
# DO NOT EDIT between here and save_cache, but rather edit ./circleci/osx_install_dependencies.sh
# WARNING! If you do edit anything here instead, remember to invalidate the cache manually.
- run:
name: Install build dependencies
command: ./.circleci/osx_install_dependencies.sh
- save_cache:
key: dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
paths:
- /usr/local/bin
- /usr/local/sbin
- /usr/local/lib
- /usr/local/include
- /usr/local/Cellar
- /usr/local/Homebrew
- when:
condition: true
<<: *steps_install_dependencies_osx
- run: *run_build
- store_artifacts: *artifacts_solc
- store_artifacts: *artifact_solidity_upgrade
@ -940,6 +932,8 @@ jobs:
- when:
condition: true
<<: *steps_install_dependencies_osx
- attach_workspace:
at: .
- run: *run_soltest
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
@ -952,6 +946,8 @@ jobs:
- when:
condition: true
<<: *steps_install_dependencies_osx
- attach_workspace:
at: .
- run: *run_cmdline_tests
- store_artifacts: *artifacts_test_results
- gitter_notify_failure_unless_pr
@ -987,7 +983,7 @@ jobs:
- run: *setup_prerelease_commit_hash
- run:
name: Build documentation
command: ./scripts/docs.sh
command: ./docs/docs.sh
- store_artifacts:
path: docs/_build/html/
destination: docs-html
@ -1480,6 +1476,7 @@ workflows:
- t_ems_ext: *job_native_test_ext_uniswap
- t_ems_ext: *job_native_test_ext_prb_math
- t_ems_ext: *job_native_test_ext_elementfi
- t_ems_ext: *job_native_test_ext_brink
- c_ext_benchmarks:
<<: *workflow_trigger_on_tags
@ -1497,6 +1494,7 @@ workflows:
- t_native_test_ext_uniswap
- t_native_test_ext_prb_math
- t_native_test_ext_elementfi
- t_native_test_ext_brink
# Windows build and tests
- b_win: *workflow_trigger_on_tags

4
.gitignore vendored
View File

@ -61,3 +61,7 @@ CMakeLists.txt.user
# place to put local temporary files
tmp
# OS specific local files
.DS_Store
Thumbs.db

View File

@ -16,9 +16,15 @@ Language Features:
Compiler Features:
* Peephole Optimizer: Remove operations without side effects before simple terminations.
* Assembly-Json: Export: Include source list in `sourceList` field.
* Commandline Interface: option ``--pretty-json`` works also with the following options: ``--abi``, ``--asm-json``, ``--ast-compact-json``, ``--devdoc``, ``--storage-layout``, ``--userdoc``.
* SMTChecker: Support ``abi.encodeCall`` taking into account the called selector.
Bugfixes:
* Assembly-Json: Fix assembly json export to store jump types of operations in `jumpType` field instead of `value`.
* TypeChecker: Convert parameters of function type to how they would be called for ``abi.encodeCall``.

View File

@ -54,9 +54,10 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
add_compile_options(-Wsign-conversion)
add_compile_options(-Wconversion)
eth_add_cxx_compiler_flag_if_supported(
$<$<COMPILE_LANGUAGE:CXX>:-Wextra-semi>
)
check_cxx_compiler_flag(-Wextra-semi WEXTRA_SEMI)
if(WEXTRA_SEMI)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wextra-semi>)
endif()
eth_add_cxx_compiler_flag_if_supported(-Wfinal-dtor-non-final-class)
eth_add_cxx_compiler_flag_if_supported(-Wnewline-eof)
eth_add_cxx_compiler_flag_if_supported(-Wsuggest-destructor-override)

View File

@ -313,7 +313,8 @@ Furthermore, if the assembly block assigns to Solidity variables in memory, you
the Solidity variables only access these memory ranges.
Since this is mainly about the optimizer, these restrictions still need to be followed, even if the assembly block
reverts or terminates. As an example, the following assembly snippet is not memory safe:
reverts or terminates. As an example, the following assembly snippet is not memory safe, because the value of
``returndatasize()`` may exceed the 64 byte scratch space:
.. code-block:: solidity
@ -322,7 +323,8 @@ reverts or terminates. As an example, the following assembly snippet is not memo
revert(0, returndatasize())
}
But the following is:
On the other hand, the following code *is* memory safe, because memory beyond the location pointed to by the
free memory pointer can safely be used as temporary scratch space:
.. code-block:: solidity

View File

@ -6,12 +6,15 @@
Abstract Contracts
******************
Contracts need to be marked as abstract when at least one of their functions is not implemented.
Contracts may be marked as abstract even though all functions are implemented.
Contracts must be marked as abstract when at least one of their functions is not implemented or when
they do not provide arguments for all of their base contract constructors.
Even if this is not the case, a contract may still be marked abstract, such as when you do not intend
for the contract to be created directly. Abstract contracts are similar to :ref:`interfaces` but an
interface is more limited in what it can declare.
This can be done by using the ``abstract`` keyword as shown in the following example. Note that this contract needs to be
defined as abstract, because the function ``utterance()`` was defined, but no implementation was
provided (no implementation body ``{ }`` was given).
An abstract contract is declared using the ``abstract`` keyword as shown in the following example.
Note that this contract needs to be defined as abstract, because the function ``utterance()`` is declared,
but no implementation was provided (no implementation body ``{ }`` was given).
.. code-block:: solidity

View File

@ -76,9 +76,9 @@ Details are given in the following example.
}
// Multiple inheritance is possible. Note that `owned` is
// Multiple inheritance is possible. Note that `Owned` is
// also a base class of `Destructible`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
// instance of `Owned` (as for virtual inheritance in C++).
contract Named is Owned, Destructible {
constructor(bytes32 name) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
@ -443,7 +443,7 @@ cannot be assigned valid values from outside but only through the constructors o
``internal`` or ``public``.
.. index:: ! base;constructor
.. index:: ! base;constructor, inheritance list, contract;abstract, abstract contract
Arguments for Base Constructors
===============================
@ -467,11 +467,20 @@ derived contracts need to specify all of them. This can be done in two ways:
constructor() {}
}
// or through a "modifier" of the derived constructor.
// or through a "modifier" of the derived constructor...
contract Derived2 is Base {
constructor(uint y) Base(y * y) {}
}
// or declare abstract...
abstract contract Derived3 is Base {
}
// and have the next concrete derived contract initialize it.
contract DerivedFromDerived is Derived3 {
constructor() Base(10 + 10) {}
}
One way is directly in the inheritance list (``is Base(7)``). The other is in
the way a modifier is invoked as part of
the derived constructor (``Base(y * y)``). The first way to
@ -484,7 +493,12 @@ inheritance list or in modifier-style in the derived constructor.
Specifying arguments in both places is an error.
If a derived contract does not specify the arguments to all of its base
contracts' constructors, it will be abstract.
contracts' constructors, it must be declared abstract. In that case, when
another contract derives from it, that other contract's inheritance list
or constructor must provide the necessary parameters
for all base classes that haven't had their parameters specified (otherwise,
that other contract must be declared abstract as well). For example, in the above
code snippet, see ``Derived3`` and ``DerivedFromDerived``.
.. index:: ! inheritance;multiple, ! linearization, ! C3 linearization

View File

@ -475,7 +475,7 @@ For example ``pragma solidity >=0.4.0 <0.9.0;``.
Running Documentation Tests
---------------------------
Make sure your contributions pass our documentation tests by running ``./scripts/docs.sh`` that installs dependencies
Make sure your contributions pass our documentation tests by running ``./docs/docs.sh`` that installs dependencies
needed for documentation and checks for any problems such as broken links or syntax issues.
Solidity Language Design

View File

@ -283,7 +283,7 @@ which only need to be created if there is a dispute.
salt,
keccak256(abi.encodePacked(
type(D).creationCode,
arg
abi.encode(arg)
))
)))));

View File

@ -26,8 +26,10 @@
# (c) 2016 solidity contributors.
#------------------------------------------------------------------------------
set -e
cd docs
set -euo pipefail
script_dir="$(dirname "$0")"
cd "${script_dir}"
pip3 install -r requirements.txt --upgrade --upgrade-strategy eager
sphinx-build -nW -b html -d _build/doctrees . _build/html
cd ..

View File

@ -396,27 +396,34 @@ returns that code when executed.
Gas
===
Upon creation, each transaction is charged with a certain amount of **gas**,
whose purpose is to limit the amount of work that is needed to execute
the transaction and to pay for this execution at the same time. While the EVM executes the
Upon creation, each transaction is charged with a certain amount of **gas**
that has to be paid for by the originator of the transaction (``tx.origin``).
While the EVM executes the
transaction, the gas is gradually depleted according to specific rules.
The **gas price** is a value set by the creator of the transaction, who
has to pay ``gas_price * gas`` up front from the sending account.
If some gas is left after the execution, it is refunded to the creator in the same way.
If the gas is used up at any point (i.e. it would be negative),
an out-of-gas exception is triggered, which reverts all modifications
an out-of-gas exception is triggered, which ends execution and reverts all modifications
made to the state in the current call frame.
This mechanism incentivizes economical use of EVM execution time
and also compensates EVM executors (i.e. miners / stakers) for their work.
Since each block has a maximum amount of gas, it also limits the amount
of work needed to validate a block.
The **gas price** is a value set by the originator of the transaction, who
has to pay ``gas_price * gas`` up front to the EVM executor.
If some gas is left after execution, it is refunded to the transaction originator.
In case of an exception that reverts changes, already used up gas is not refunded.
Since EVM executors can choose to include a transaction or not,
transaction senders cannot abuse the system by setting a low gas price.
.. index:: ! storage, ! memory, ! stack
Storage, Memory and the Stack
=============================
The Ethereum Virtual Machine has three areas where it can store data-
storage, memory and the stack, which are explained in the following
paragraphs.
The Ethereum Virtual Machine has three areas where it can store data:
storage, memory and the stack.
Each account has a data area called **storage**, which is persistent between function calls
and transactions.
@ -504,7 +511,7 @@ Delegatecall / Callcode and Libraries
There exists a special variant of a message call, named **delegatecall**
which is identical to a message call apart from the fact that
the code at the target address is executed in the context of the calling
the code at the target address is executed in the context (i.e. at the address) of the calling
contract and ``msg.sender`` and ``msg.value`` do not change their values.
This means that a contract can dynamically load code from a different

View File

@ -119,7 +119,7 @@ Iterable Mappings
You cannot iterate over mappings, i.e. you cannot enumerate their keys.
It is possible, though, to implement a data structure on
top of them and iterate over that. For example, the code below implements an
``IterableMapping`` library that the ``User`` contract then adds data too, and
``IterableMapping`` library that the ``User`` contract then adds data to, and
the ``sum`` function iterates over to sum all the values.
.. code-block:: solidity

View File

@ -463,7 +463,7 @@ There is no additional semantic meaning added to a number literal containing und
the underscores are ignored.
Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by
using them together with anything else than a number literal expression (like boolean literals) or by explicit conversion).
using them together with anything other than a number literal expression (like boolean literals) or by explicit conversion).
This means that computations do not overflow and divisions do not truncate
in number literal expressions.

View File

@ -222,123 +222,59 @@ string Assembly::assemblyString(
return tmp.str();
}
Json::Value Assembly::createJsonValue(string _name, int _source, int _begin, int _end, string _value, string _jumpType)
{
Json::Value value{Json::objectValue};
value["name"] = _name;
value["source"] = _source;
value["begin"] = _begin;
value["end"] = _end;
if (!_value.empty())
value["value"] = _value;
if (!_jumpType.empty())
value["jumpType"] = _jumpType;
return value;
}
string Assembly::toStringInHex(u256 _value)
{
std::stringstream hexStr;
hexStr << std::uppercase << hex << _value;
return hexStr.str();
}
Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices) const
Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices, bool _includeSourceList) const
{
Json::Value root;
root[".code"] = Json::arrayValue;
Json::Value& collection = root[".code"];
for (AssemblyItem const& i: m_items)
Json::Value& code = root[".code"];
for (AssemblyItem const& item: m_items)
{
int sourceIndex = -1;
if (i.location().sourceName)
if (item.location().sourceName)
{
auto iter = _sourceIndices.find(*i.location().sourceName);
auto iter = _sourceIndices.find(*item.location().sourceName);
if (iter != _sourceIndices.end())
sourceIndex = static_cast<int>(iter->second);
}
switch (i.type())
auto [name, data] = item.nameAndData();
Json::Value jsonItem;
jsonItem["name"] = name;
jsonItem["begin"] = item.location().start;
jsonItem["end"] = item.location().end;
if (item.m_modifierDepth != 0)
jsonItem["modifierDepth"] = static_cast<int>(item.m_modifierDepth);
std::string jumpType = item.getJumpTypeAsString();
if (!jumpType.empty())
jsonItem["jumpType"] = jumpType;
if (name == "PUSHLIB")
data = m_libraries.at(h256(data));
else if (name == "PUSHIMMUTABLE" || name == "ASSIGNIMMUTABLE")
data = m_immutables.at(h256(data));
if (!data.empty())
jsonItem["value"] = data;
jsonItem["source"] = sourceIndex;
code.append(move(jsonItem));
if (item.type() == AssemblyItemType::Tag)
{
case Operation:
collection.append(
createJsonValue(
instructionInfo(i.instruction()).name,
sourceIndex,
i.location().start,
i.location().end,
i.getJumpTypeAsString())
);
break;
case Push:
collection.append(
createJsonValue("PUSH", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString()));
break;
case PushTag:
if (i.data() == 0)
collection.append(
createJsonValue("PUSH [ErrorTag]", sourceIndex, i.location().start, i.location().end, ""));
else
collection.append(
createJsonValue("PUSH [tag]", sourceIndex, i.location().start, i.location().end, toString(i.data())));
break;
case PushSub:
collection.append(
createJsonValue("PUSH [$]", sourceIndex, i.location().start, i.location().end, toString(h256(i.data()))));
break;
case PushSubSize:
collection.append(
createJsonValue("PUSH #[$]", sourceIndex, i.location().start, i.location().end, toString(h256(i.data()))));
break;
case PushProgramSize:
collection.append(
createJsonValue("PUSHSIZE", sourceIndex, i.location().start, i.location().end));
break;
case PushLibraryAddress:
collection.append(
createJsonValue("PUSHLIB", sourceIndex, i.location().start, i.location().end, m_libraries.at(h256(i.data())))
);
break;
case PushDeployTimeAddress:
collection.append(
createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
);
break;
case PushImmutable:
collection.append(createJsonValue(
"PUSHIMMUTABLE",
sourceIndex,
i.location().start,
i.location().end,
m_immutables.at(h256(i.data()))
));
break;
case AssignImmutable:
collection.append(createJsonValue(
"ASSIGNIMMUTABLE",
sourceIndex,
i.location().start,
i.location().end,
m_immutables.at(h256(i.data()))
));
break;
case Tag:
collection.append(
createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data())));
collection.append(
createJsonValue("JUMPDEST", sourceIndex, i.location().start, i.location().end));
break;
case PushData:
collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
break;
case VerbatimBytecode:
collection.append(createJsonValue("VERBATIM", sourceIndex, i.location().start, i.location().end, util::toHex(i.verbatimData())));
break;
default:
assertThrow(false, InvalidOpcode, "");
Json::Value jumpdest;
jumpdest["name"] = "JUMPDEST";
jumpdest["begin"] = item.location().start;
jumpdest["end"] = item.location().end;
jumpdest["source"] = sourceIndex;
if (item.m_modifierDepth != 0)
jumpdest["modifierDepth"] = static_cast<int>(item.m_modifierDepth);
code.append(move(jumpdest));
}
}
if (_includeSourceList)
{
root["sourceList"] = Json::arrayValue;
Json::Value& jsonSourceList = root["sourceList"];
for (auto const& [name, index]: _sourceIndices)
jsonSourceList[index] = name;
}
if (!m_data.empty() || !m_subs.empty())
{
@ -346,17 +282,17 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
Json::Value& data = root[".data"];
for (auto const& i: m_data)
if (u256(i.first) >= m_subs.size())
data[toStringInHex((u256)i.first)] = util::toHex(i.second);
data[util::toHex(toBigEndian((u256)i.first), util::HexPrefix::DontAdd, util::HexCase::Upper)] = util::toHex(i.second);
for (size_t i = 0; i < m_subs.size(); ++i)
{
std::stringstream hexStr;
hexStr << hex << i;
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices);
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices, /*_includeSourceList = */false);
}
}
if (m_auxiliaryData.size() > 0)
if (!m_auxiliaryData.empty())
root[".auxdata"] = util::toHex(m_auxiliaryData);
return root;

View File

@ -39,6 +39,7 @@
#include <sstream>
#include <memory>
#include <map>
#include <utility>
namespace solidity::evmasm
{
@ -147,7 +148,8 @@ public:
/// Create a JSON representation of the assembly.
Json::Value assemblyJSON(
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>(),
bool _includeSourceList = true
) const;
/// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
@ -167,16 +169,6 @@ protected:
unsigned codeSize(unsigned subTagSize) const;
private:
static Json::Value createJsonValue(
std::string _name,
int _source,
int _begin,
int _end,
std::string _value = std::string(),
std::string _jumpType = std::string()
);
static std::string toStringInHex(u256 _value);
bool m_invalid = false;
Assembly const* subAssemblyById(size_t _subId) const;
@ -222,6 +214,7 @@ protected:
std::string m_name;
langutil::SourceLocation m_currentSourceLocation;
public:
size_t m_currentModifierDepth = 0;
};

View File

@ -21,6 +21,7 @@
#include <libevmasm/Assembly.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/Numeric.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/FixedHash.h>
@ -36,6 +37,18 @@ using namespace solidity::langutil;
static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide");
namespace
{
string toStringInHex(u256 _value)
{
std::stringstream hexStr;
hexStr << std::uppercase << hex << _value;
return hexStr.str();
}
}
AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const
{
assertThrow(data() < (u256(1) << 64), util::Exception, "Tag already has subassembly set.");
@ -56,6 +69,44 @@ pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
return make_pair(subId, tag);
}
pair<string, string> AssemblyItem::nameAndData() const
{
switch (type())
{
case Operation:
return {instructionInfo(instruction()).name, m_data != nullptr ? toStringInHex(*m_data) : ""};
case Push:
return {"PUSH", toStringInHex(data())};
case PushTag:
if (data() == 0)
return {"PUSH [ErrorTag]", ""};
else
return {"PUSH [tag]", util::toString(data())};
case PushSub:
return {"PUSH [$]", toString(util::h256(data()))};
case PushSubSize:
return {"PUSH #[$]", toString(util::h256(data()))};
case PushProgramSize:
return {"PUSHSIZE", ""};
case PushLibraryAddress:
return {"PUSHLIB", toString(util::h256(data()))};
case PushDeployTimeAddress:
return {"PUSHDEPLOYADDRESS", ""};
case PushImmutable:
return {"PUSHIMMUTABLE", toString(util::h256(data()))};
case AssignImmutable:
return {"ASSIGNIMMUTABLE", toString(util::h256(data()))};
case Tag:
return {"tag", util::toString(data())};
case PushData:
return {"PUSH data", toStringInHex(data())};
case VerbatimBytecode:
return {"VERBATIM", util::toHex(verbatimData())};
default:
assertThrow(false, InvalidOpcode, "");
}
}
void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
{
assertThrow(m_type == PushTag || m_type == Tag, util::Exception, "");

View File

@ -106,6 +106,13 @@ public:
u256 const& data() const { assertThrow(m_type != Operation, util::Exception, ""); return *m_data; }
void setData(u256 const& _data) { assertThrow(m_type != Operation, util::Exception, ""); m_data = std::make_shared<u256>(_data); }
/// This function is used in `Assembly::assemblyJSON`.
/// It returns the name & data of the current assembly item.
/// @returns a pair, where the first element is the json-assembly
/// item name, where second element is the string representation
/// of it's data.
std::pair<std::string, std::string> nameAndData() const;
bytes const& verbatimData() const { assertThrow(m_type == VerbatimBytecode, util::Exception, ""); return std::get<2>(*m_verbatimBytecode); }
/// @returns the instruction of this item (only valid if type() == Operation)

View File

@ -146,6 +146,35 @@ struct OpStop: SimplePeepholeOptimizerMethod<OpStop>
}
};
struct OpReturnRevert: SimplePeepholeOptimizerMethod<OpReturnRevert>
{
static bool applySimple(
AssemblyItem const& _op,
AssemblyItem const& _push,
AssemblyItem const& _pushOrDup,
AssemblyItem const& _returnRevert,
std::back_insert_iterator<AssemblyItems> _out
)
{
if (
(_returnRevert == Instruction::RETURN || _returnRevert == Instruction::REVERT) &&
_push.type() == Push &&
(_pushOrDup.type() == Push || _pushOrDup == dupInstruction(1))
)
if (
(_op.type() == Operation && !instructionInfo(_op.instruction()).sideEffects) ||
_op.type() == Push
)
{
*_out = _push;
*_out = _pushOrDup;
*_out = _returnRevert;
return true;
}
return false;
}
};
struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap>
{
static size_t applySimple(AssemblyItem const& _s1, AssemblyItem const& _s2, std::back_insert_iterator<AssemblyItems>)
@ -459,7 +488,7 @@ bool PeepholeOptimiser::optimise()
while (state.i < m_items.size())
applyMethods(
state,
PushPop(), OpPop(), OpStop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
PushPop(), OpPop(), OpStop(), OpReturnRevert(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
DupSwap(), IsZeroIsZeroJumpI(), EqIsZeroJumpI(), DoubleJump(), JumpToNext(), UnreachableCode(),
TagConjunctions(), TruthyAnd(), Identity()
);

View File

@ -58,13 +58,13 @@ struct InvalidAstError: virtual util::Exception {};
#endif
#define solAssert_1(CONDITION) \
solAssert_2(CONDITION, "")
solAssert_2((CONDITION), "")
#define solAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
(CONDITION), \
::solidity::langutil::InternalCompilerError, \
DESCRIPTION, \
(DESCRIPTION), \
"Solidity assertion failed" \
)
@ -77,13 +77,13 @@ struct InvalidAstError: virtual util::Exception {};
#endif
#define solUnimplementedAssert_1(CONDITION) \
solUnimplementedAssert_2(CONDITION, "")
solUnimplementedAssert_2((CONDITION), "")
#define solUnimplementedAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
(CONDITION), \
::solidity::langutil::UnimplementedFeatureError, \
DESCRIPTION, \
(DESCRIPTION), \
"Unimplemented feature" \
)
@ -105,9 +105,9 @@ struct InvalidAstError: virtual util::Exception {};
#define astAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
(CONDITION), \
::solidity::langutil::InvalidAstError, \
DESCRIPTION, \
(DESCRIPTION), \
"AST assertion failed" \
)

View File

@ -38,13 +38,13 @@ struct SMTLogicError: virtual util::Exception {};
#endif
#define smtAssert_1(CONDITION) \
smtAssert_2(CONDITION, "")
smtAssert_2((CONDITION), "")
#define smtAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
(CONDITION), \
::solidity::smtutil::SMTLogicError, \
DESCRIPTION, \
(DESCRIPTION), \
"SMT assertion failed" \
)

View File

@ -297,7 +297,7 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
else if (optional<ConstantEvaluator::TypedRational> value = ConstantEvaluator::evaluate(m_errorReporter, *length))
lengthValue = value->value;
if (!lengthValue || lengthValue > TypeProvider::uint256()->max())
if (!lengthValue)
m_errorReporter.typeError(
5462_error,
length->location(),
@ -309,6 +309,12 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
m_errorReporter.typeError(3208_error, length->location(), "Array with fractional length specified.");
else if (*lengthValue < 0)
m_errorReporter.typeError(3658_error, length->location(), "Array with negative length specified.");
else if (lengthValue > TypeProvider::uint256()->max())
m_errorReporter.typeError(
1847_error,
length->location(),
"Array length too large, maximum is 2**256 - 1."
);
_typeName.annotation().type = TypeProvider::array(
DataLocation::Storage,

View File

@ -1658,24 +1658,21 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
Type const* subExprType = type(_operation.subExpression());
TypeResult result = subExprType->unaryOperatorResult(op);
Type const* t = result;
if (!result)
{
string description = "Unary operator " +
string(TokenTraits::toString(op)) +
" cannot be applied to type " +
subExprType->toString() +
(result.message().empty() ? "" : (": " + result.message()));
string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->toString();
if (!result.message().empty())
description += ". " + result.message();
if (modifying)
// Cannot just report the error, ignore the unary operator, and continue,
// because the sub-expression was already processed with requireLValue()
m_errorReporter.fatalTypeError(9767_error, _operation.location(), description);
else
m_errorReporter.typeError(4907_error, _operation.location(), description);
t = subExprType;
_operation.annotation().type = subExprType;
}
_operation.annotation().type = t;
else
_operation.annotation().type = result.get();
_operation.annotation().isConstant = false;
_operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure;
_operation.annotation().isLValue = false;
@ -2111,57 +2108,60 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
return;
}
auto const functionPointerType = dynamic_cast<FunctionTypePointer>(type(*arguments.front()));
if (!functionPointerType)
FunctionType const* externalFunctionType = nullptr;
if (auto const functionPointerType = dynamic_cast<FunctionTypePointer>(type(*arguments.front())))
{
// this cannot be a library function, that is checked below
externalFunctionType = functionPointerType->asExternallyCallableFunction(false);
solAssert(externalFunctionType->kind() == functionPointerType->kind());
}
else
{
m_errorReporter.typeError(
5511_error,
arguments.front()->location(),
"Expected first argument to be a function pointer, not \"" +
type(*arguments.front())->canonicalName() +
type(*arguments.front())->toString() +
"\"."
);
return;
}
if (
functionPointerType->kind() != FunctionType::Kind::External &&
functionPointerType->kind() != FunctionType::Kind::Declaration
externalFunctionType->kind() != FunctionType::Kind::External &&
externalFunctionType->kind() != FunctionType::Kind::Declaration
)
{
string msg = "Expected regular external function type, or external view on public function.";
if (functionPointerType->kind() == FunctionType::Kind::Internal)
if (externalFunctionType->kind() == FunctionType::Kind::Internal)
msg += " Provided internal function.";
else if (functionPointerType->kind() == FunctionType::Kind::DelegateCall)
else if (externalFunctionType->kind() == FunctionType::Kind::DelegateCall)
msg += " Cannot use library functions for abi.encodeCall.";
else if (functionPointerType->kind() == FunctionType::Kind::Creation)
else if (externalFunctionType->kind() == FunctionType::Kind::Creation)
msg += " Provided creation function.";
else
msg += " Cannot use special function.";
SecondarySourceLocation ssl{};
if (functionPointerType->hasDeclaration())
if (externalFunctionType->hasDeclaration())
{
ssl.append("Function is declared here:", functionPointerType->declaration().location());
ssl.append("Function is declared here:", externalFunctionType->declaration().location());
if (
functionPointerType->declaration().visibility() == Visibility::Public &&
functionPointerType->declaration().scope() == m_currentContract
externalFunctionType->declaration().visibility() == Visibility::Public &&
externalFunctionType->declaration().scope() == m_currentContract
)
msg += " Did you forget to prefix \"this.\"?";
else if (util::contains(
m_currentContract->annotation().linearizedBaseContracts,
functionPointerType->declaration().scope()
) && functionPointerType->declaration().scope() != m_currentContract)
externalFunctionType->declaration().scope()
) && externalFunctionType->declaration().scope() != m_currentContract)
msg += " Functions from base contracts have to be external.";
}
m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg);
return;
}
solAssert(!functionPointerType->takesArbitraryParameters(), "Function must have fixed parameters.");
solAssert(!externalFunctionType->takesArbitraryParameters(), "Function must have fixed parameters.");
// Tuples with only one component become that component
vector<ASTPointer<Expression const>> callArguments;
@ -2174,14 +2174,14 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
else
callArguments.push_back(arguments[1]);
if (functionPointerType->parameterTypes().size() != callArguments.size())
if (externalFunctionType->parameterTypes().size() != callArguments.size())
{
if (tupleType)
m_errorReporter.typeError(
7788_error,
_functionCall.location(),
"Expected " +
to_string(functionPointerType->parameterTypes().size()) +
to_string(externalFunctionType->parameterTypes().size()) +
" instead of " +
to_string(callArguments.size()) +
" components for the tuple parameter."
@ -2191,18 +2191,18 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
7515_error,
_functionCall.location(),
"Expected a tuple with " +
to_string(functionPointerType->parameterTypes().size()) +
to_string(externalFunctionType->parameterTypes().size()) +
" components instead of a single non-tuple parameter."
);
}
// Use min() to check as much as we can before failing fatally
size_t const numParameters = min(callArguments.size(), functionPointerType->parameterTypes().size());
size_t const numParameters = min(callArguments.size(), externalFunctionType->parameterTypes().size());
for (size_t i = 0; i < numParameters; i++)
{
Type const& argType = *type(*callArguments[i]);
BoolResult result = argType.isImplicitlyConvertibleTo(*functionPointerType->parameterTypes()[i]);
BoolResult result = argType.isImplicitlyConvertibleTo(*externalFunctionType->parameterTypes()[i]);
if (!result)
m_errorReporter.typeError(
5407_error,
@ -2212,7 +2212,7 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
" from \"" +
argType.toString() +
"\" to \"" +
functionPointerType->parameterTypes()[i]->toString() +
externalFunctionType->parameterTypes()[i]->toString() +
"\"" +
(result.message().empty() ? "." : ": " + result.message())
);

View File

@ -190,9 +190,9 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifie
return tuple;
}
void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node)
void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node, util::JsonFormat const& _format)
{
_stream << util::jsonPrettyPrint(toJson(_node));
_stream << util::jsonPrint(toJson(_node), _format);
}
Json::Value ASTJsonConverter::toJson(ASTNode const& _node)

View File

@ -29,6 +29,7 @@
#include <liblangutil/Exceptions.h>
#include <json/json.h>
#include <libsolutil/JSON.h>
#include <algorithm>
#include <optional>
@ -58,7 +59,7 @@ public:
std::map<std::string, unsigned> _sourceIndices = std::map<std::string, unsigned>()
);
/// Output the json representation of the AST to _stream.
void print(std::ostream& _stream, ASTNode const& _node);
void print(std::ostream& _stream, ASTNode const& _node, util::JsonFormat const& _format);
Json::Value toJson(ASTNode const& _node);
template <class T>
Json::Value toJson(std::vector<ASTPointer<T>> const& _nodes)

View File

@ -30,7 +30,7 @@
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <libyul/Utilities.h>
#include <libsolutil/Algorithms.h>
@ -95,9 +95,9 @@ pair<string, string> IRGenerator::run(
{
string ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
yul::AssemblyStack asmStack(
yul::YulStack asmStack(
m_evmVersion,
yul::AssemblyStack::Language::StrictAssembly,
yul::YulStack::Language::StrictAssembly,
m_optimiserSettings,
m_context.debugInfoSelection()
);

View File

@ -586,6 +586,16 @@ bool SMTEncoder::visit(FunctionCall const& _funCall)
arg->accept(*this);
return false;
}
else if (funType.kind() == FunctionType::Kind::ABIEncodeCall)
{
auto fun = _funCall.arguments().front();
createExpr(*fun);
auto const* functionType = dynamic_cast<FunctionType const*>(fun->annotation().type);
if (functionType->hasDeclaration())
defineExpr(*fun, functionType->externalIdentifier());
return true;
}
// We do not really need to visit the expression in a wrap/unwrap no-op call,
// so we just ignore the function call expression to avoid "unsupported" warnings.
else if (
@ -1323,6 +1333,7 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
auto const& exprType = memberExpr->annotation().type;
solAssert(exprType, "");
if (exprType->category() == Type::Category::Magic)
{
if (auto const* identifier = dynamic_cast<Identifier const*>(memberExpr))

View File

@ -62,7 +62,7 @@
#include <libyul/YulString.h>
#include <libyul/AsmPrinter.h>
#include <libyul/AsmJsonConverter.h>
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <libyul/AST.h>
#include <libyul/AsmParser.h>
@ -1382,9 +1382,9 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
return;
// Re-parse the Yul IR in EVM dialect
yul::AssemblyStack stack(
yul::YulStack stack(
m_evmVersion,
yul::AssemblyStack::Language::StrictAssembly,
yul::YulStack::Language::StrictAssembly,
m_optimiserSettings,
m_debugInfoSelection
);
@ -1414,22 +1414,22 @@ void CompilerStack::generateEwasm(ContractDefinition const& _contract)
return;
// Re-parse the Yul IR in EVM dialect
yul::AssemblyStack stack(
yul::YulStack stack(
m_evmVersion,
yul::AssemblyStack::Language::StrictAssembly,
yul::YulStack::Language::StrictAssembly,
m_optimiserSettings,
m_debugInfoSelection
);
stack.parseAndAnalyze("", compiledContract.yulIROptimized);
stack.optimize();
stack.translate(yul::AssemblyStack::Language::Ewasm);
stack.translate(yul::YulStack::Language::Ewasm);
stack.optimize();
//cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl;
// Turn into Ewasm text representation.
auto result = stack.assemble(yul::AssemblyStack::Machine::Ewasm);
auto result = stack.assemble(yul::YulStack::Machine::Ewasm);
compiledContract.ewasm = std::move(result.assembly);
compiledContract.ewasmObject = std::move(*result.bytecode);
}

View File

@ -25,7 +25,7 @@
#include <libsolidity/interface/ImportRemapper.h>
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <libyul/Exceptions.h>
#include <libyul/optimiser/Suite.h>
@ -1407,9 +1407,9 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
return output;
}
AssemblyStack stack(
YulStack stack(
_inputsAndSettings.evmVersion,
AssemblyStack::Language::StrictAssembly,
YulStack::Language::StrictAssembly,
_inputsAndSettings.optimiserSettings,
_inputsAndSettings.debugInfoSelection.has_value() ?
_inputsAndSettings.debugInfoSelection.value() :

View File

@ -76,6 +76,7 @@ LanguageServer::LanguageServer(Transport& _transport):
{"exit", [this](auto, auto) { m_state = (m_state == State::ShutdownRequested ? State::ExitRequested : State::ExitWithoutShutdown); }},
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
{"initialized", [](auto, auto) {}},
{"$/setTrace", bind(&LanguageServer::setTrace, this, _2)},
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
{"textDocument/definition", GotoDefinition(*this) },
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
@ -166,6 +167,13 @@ void LanguageServer::compileAndUpdateDiagnostics()
diagnosticsBySourceUnit[*location->sourceName].append(jsonDiag);
}
if (m_client.traceValue() != TraceValue::Off)
{
Json::Value extra;
extra["openFileCount"] = Json::UInt64(diagnosticsBySourceUnit.size());
m_client.trace("Number of currently open files: " + to_string(diagnosticsBySourceUnit.size()), extra);
}
m_nonemptyDiagnostics.clear();
for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit)
{
@ -273,6 +281,21 @@ void LanguageServer::handleWorkspaceDidChangeConfiguration(Json::Value const& _a
changeConfiguration(_args["settings"]);
}
void LanguageServer::setTrace(Json::Value const& _args)
{
if (!_args["value"].isString())
// Simply ignore invalid parameter.
return;
string const stringValue = _args["value"].asString();
if (stringValue == "off")
m_client.setTrace(TraceValue::Off);
else if (stringValue == "messages")
m_client.setTrace(TraceValue::Messages);
else if (stringValue == "verbose")
m_client.setTrace(TraceValue::Verbose);
}
void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args)
{
requireServerInitialized();

View File

@ -68,6 +68,7 @@ private:
void requireServerInitialized();
void handleInitialize(MessageID _id, Json::Value const& _args);
void handleWorkspaceDidChangeConfiguration(Json::Value const& _args);
void setTrace(Json::Value const& _args);
void handleTextDocumentDidOpen(Json::Value const& _args);
void handleTextDocumentDidChange(Json::Value const& _args);
void handleTextDocumentDidClose(Json::Value const& _args);

View File

@ -98,6 +98,18 @@ void IOStreamTransport::error(MessageID _id, ErrorCode _code, string _message)
send(move(json), _id);
}
void Transport::trace(std::string _message, Json::Value _extra)
{
if (m_logTrace != TraceValue::Off)
{
Json::Value params;
if (_extra.isObject())
params = move(_extra);
params["message"] = move(_message);
notify("$/logTrace", move(params));
}
}
void IOStreamTransport::send(Json::Value _json, MessageID _id)
{
solAssert(_json.isObject());

View File

@ -34,6 +34,13 @@ namespace solidity::lsp
using MessageID = Json::Value;
enum class TraceValue
{
Off,
Messages,
Verbose
};
enum class ErrorCode
{
// Defined by JSON RPC
@ -89,6 +96,15 @@ public:
virtual void notify(std::string _method, Json::Value _params) = 0;
virtual void reply(MessageID _id, Json::Value _result) = 0;
virtual void error(MessageID _id, ErrorCode _code, std::string _message) = 0;
void trace(std::string _message, Json::Value _extra = Json::nullValue);
TraceValue traceValue() const noexcept { return m_logTrace; }
void setTrace(TraceValue _value) noexcept { m_logTrace = _value; }
private:
TraceValue m_logTrace = TraceValue::Off;
};
/**

View File

@ -63,7 +63,7 @@ inline std::string stringOrDefault(std::string _string, std::string _defaultStri
if (!(_condition)) \
solThrow( \
_exceptionType, \
::solidity::util::assertions::stringOrDefault(_description, _defaultDescription) \
::solidity::util::assertions::stringOrDefault((_description), (_defaultDescription)) \
); \
} \
while (false)
@ -72,6 +72,6 @@ inline std::string stringOrDefault(std::string _string, std::string _defaultStri
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
/// The second parameter must be an exception class (rather than an instance).
#define assertThrow(_condition, _exceptionType, _description) \
assertThrowWithDefaultDescription(_condition, _exceptionType, _description, "Assertion failed")
assertThrowWithDefaultDescription((_condition), _exceptionType, (_description), "Assertion failed")
}

View File

@ -48,7 +48,7 @@ struct Exception: virtual std::exception, virtual boost::exception
#define solThrow(_exceptionType, _description) \
::boost::throw_exception( \
_exceptionType() << \
::solidity::util::errinfo_comment(_description) << \
::solidity::util::errinfo_comment((_description)) << \
::boost::throw_function(ETH_FUNC) << \
::boost::throw_file(__FILE__) << \
::boost::throw_line(__LINE__) \

View File

@ -30,8 +30,8 @@ add_library(yul
AsmParser.h
AsmPrinter.cpp
AsmPrinter.h
AssemblyStack.h
AssemblyStack.cpp
YulStack.h
YulStack.cpp
CompilabilityChecker.cpp
CompilabilityChecker.h
ControlFlowSideEffects.h

View File

@ -63,13 +63,13 @@ struct StackTooDeepError: virtual YulException
#endif
#define yulAssert_1(CONDITION) \
yulAssert_2(CONDITION, "")
yulAssert_2((CONDITION), "")
#define yulAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
(CONDITION), \
::solidity::yul::YulAssertion, \
DESCRIPTION, \
(DESCRIPTION), \
"Yul assertion failed" \
)

View File

@ -21,7 +21,7 @@
*/
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <libyul/AsmAnalysis.h>
#include <libyul/AsmAnalysisInfo.h>
@ -48,16 +48,16 @@ using namespace solidity::langutil;
namespace
{
Dialect const& languageToDialect(AssemblyStack::Language _language, EVMVersion _version)
Dialect const& languageToDialect(YulStack::Language _language, EVMVersion _version)
{
switch (_language)
{
case AssemblyStack::Language::Assembly:
case AssemblyStack::Language::StrictAssembly:
case YulStack::Language::Assembly:
case YulStack::Language::StrictAssembly:
return EVMDialect::strictAssemblyForEVMObjects(_version);
case AssemblyStack::Language::Yul:
case YulStack::Language::Yul:
return EVMDialectTyped::instance(_version);
case AssemblyStack::Language::Ewasm:
case YulStack::Language::Ewasm:
return WasmDialect::instance();
}
yulAssert(false, "");
@ -88,14 +88,14 @@ evmasm::Assembly::OptimiserSettings translateOptimiserSettings(
}
CharStream const& AssemblyStack::charStream(string const& _sourceName) const
CharStream const& YulStack::charStream(string const& _sourceName) const
{
yulAssert(m_charStream, "");
yulAssert(m_charStream->name() == _sourceName, "");
return *m_charStream;
}
bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source)
bool YulStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source)
{
m_errors.clear();
m_analysisSuccessful = false;
@ -110,7 +110,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string
return analyzeParsed();
}
void AssemblyStack::optimize()
void YulStack::optimize()
{
if (!m_optimiserSettings.runYulOptimiser)
return;
@ -123,7 +123,7 @@ void AssemblyStack::optimize()
yulAssert(analyzeParsed(), "Invalid source code after optimization.");
}
void AssemblyStack::translate(AssemblyStack::Language _targetLanguage)
void YulStack::translate(YulStack::Language _targetLanguage)
{
if (m_language == _targetLanguage)
return;
@ -141,14 +141,14 @@ void AssemblyStack::translate(AssemblyStack::Language _targetLanguage)
m_language = _targetLanguage;
}
bool AssemblyStack::analyzeParsed()
bool YulStack::analyzeParsed()
{
yulAssert(m_parserResult, "");
m_analysisSuccessful = analyzeParsed(*m_parserResult);
return m_analysisSuccessful;
}
bool AssemblyStack::analyzeParsed(Object& _object)
bool YulStack::analyzeParsed(Object& _object)
{
yulAssert(_object.code, "");
_object.analysisInfo = make_shared<AsmAnalysisInfo>();
@ -168,7 +168,7 @@ bool AssemblyStack::analyzeParsed(Object& _object)
return success;
}
void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) const
void YulStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) const
{
EVMDialect const* dialect = nullptr;
switch (m_language)
@ -188,7 +188,7 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) cons
EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _optimize);
}
void AssemblyStack::optimize(Object& _object, bool _isCreation)
void YulStack::optimize(Object& _object, bool _isCreation)
{
yulAssert(_object.code, "");
yulAssert(_object.analysisInfo, "");
@ -214,7 +214,7 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
);
}
MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
MachineAssemblyObject YulStack::assemble(Machine _machine) const
{
yulAssert(m_analysisSuccessful, "");
yulAssert(m_parserResult, "");
@ -243,7 +243,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
}
std::pair<MachineAssemblyObject, MachineAssemblyObject>
AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
YulStack::assembleWithDeployed(optional<string_view> _deployName) const
{
auto [creationAssembly, deployedAssembly] = assembleEVMWithDeployed(_deployName);
yulAssert(creationAssembly, "");
@ -277,7 +277,7 @@ AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
}
std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>>
AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
YulStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
{
yulAssert(m_analysisSuccessful, "");
yulAssert(m_parserResult, "");
@ -317,7 +317,7 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
return {make_shared<evmasm::Assembly>(assembly), {}};
}
string AssemblyStack::print(
string YulStack::print(
CharStreamProvider const* _soliditySourceProvider
) const
{
@ -326,7 +326,7 @@ string AssemblyStack::print(
return m_parserResult->toString(&languageToDialect(m_language, m_evmVersion), m_debugInfoSelection, _soliditySourceProvider) + "\n";
}
shared_ptr<Object> AssemblyStack::parserResult() const
shared_ptr<Object> YulStack::parserResult() const
{
yulAssert(m_analysisSuccessful, "Analysis was not successful.");
yulAssert(m_parserResult, "");

View File

@ -63,14 +63,14 @@ struct MachineAssemblyObject
* Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and
* Ewasm as output.
*/
class AssemblyStack: public langutil::CharStreamProvider
class YulStack: public langutil::CharStreamProvider
{
public:
enum class Language { Yul, Assembly, StrictAssembly, Ewasm };
enum class Machine { EVM, Ewasm };
AssemblyStack():
AssemblyStack(
YulStack():
YulStack(
langutil::EVMVersion{},
Language::Assembly,
solidity::frontend::OptimiserSettings::none(),
@ -78,7 +78,7 @@ public:
)
{}
AssemblyStack(
YulStack(
langutil::EVMVersion _evmVersion,
Language _language,
solidity::frontend::OptimiserSettings _optimiserSettings,

View File

@ -30,18 +30,22 @@ using namespace solidity::util;
void NameCollector::operator()(VariableDeclaration const& _varDecl)
{
for (auto const& var: _varDecl.variables)
m_names.emplace(var.name);
if (m_collectWhat != OnlyFunctions)
for (auto const& var: _varDecl.variables)
m_names.emplace(var.name);
}
void NameCollector::operator ()(FunctionDefinition const& _funDef)
void NameCollector::operator()(FunctionDefinition const& _funDef)
{
if (m_collectWhat == VariablesAndFunctions)
if (m_collectWhat != OnlyVariables)
m_names.emplace(_funDef.name);
for (auto const& arg: _funDef.parameters)
m_names.emplace(arg.name);
for (auto const& ret: _funDef.returnVariables)
m_names.emplace(ret.name);
if (m_collectWhat != OnlyFunctions)
{
for (auto const& arg: _funDef.parameters)
m_names.emplace(arg.name);
for (auto const& ret: _funDef.returnVariables)
m_names.emplace(ret.name);
}
ASTWalker::operator ()(_funDef);
}

View File

@ -35,7 +35,7 @@ namespace solidity::yul
class NameCollector: public ASTWalker
{
public:
enum CollectWhat { VariablesAndFunctions, OnlyVariables };
enum CollectWhat { VariablesAndFunctions, OnlyVariables, OnlyFunctions };
explicit NameCollector(
Block const& _block,

View File

@ -37,16 +37,6 @@ void Rematerialiser::run(Dialect const& _dialect, Block& _ast, set<YulString> _v
Rematerialiser{_dialect, _ast, std::move(_varsToAlwaysRematerialize), _onlySelectedVariables}(_ast);
}
void Rematerialiser::run(
Dialect const& _dialect,
FunctionDefinition& _function,
set<YulString> _varsToAlwaysRematerialize,
bool _onlySelectedVariables
)
{
Rematerialiser{_dialect, _function, std::move(_varsToAlwaysRematerialize), _onlySelectedVariables}(_function);
}
Rematerialiser::Rematerialiser(
Dialect const& _dialect,
Block& _ast,
@ -60,19 +50,6 @@ Rematerialiser::Rematerialiser(
{
}
Rematerialiser::Rematerialiser(
Dialect const& _dialect,
FunctionDefinition& _function,
set<YulString> _varsToAlwaysRematerialize,
bool _onlySelectedVariables
):
DataFlowAnalyzer(_dialect),
m_referenceCounts(ReferencesCounter::countReferences(_function)),
m_varsToAlwaysRematerialize(std::move(_varsToAlwaysRematerialize)),
m_onlySelectedVariables(_onlySelectedVariables)
{
}
void Rematerialiser::visit(Expression& _e)
{
if (holds_alternative<Identifier>(_e))

View File

@ -53,12 +53,6 @@ public:
std::set<YulString> _varsToAlwaysRematerialize = {},
bool _onlySelectedVariables = false
);
static void run(
Dialect const& _dialect,
FunctionDefinition& _function,
std::set<YulString> _varsToAlwaysRematerialize = {},
bool _onlySelectedVariables = false
);
protected:
Rematerialiser(
@ -67,12 +61,6 @@ protected:
std::set<YulString> _varsToAlwaysRematerialize = {},
bool _onlySelectedVariables = false
);
Rematerialiser(
Dialect const& _dialect,
FunctionDefinition& _function,
std::set<YulString> _varsToAlwaysRematerialize = {},
bool _onlySelectedVariables = false
);
using DataFlowAnalyzer::operator();

View File

@ -50,31 +50,40 @@ namespace
/**
* Class that discovers all variables that can be fully eliminated by rematerialization,
* and the corresponding approximate costs.
*
* Prerequisite: Disambiguator, Function Grouper
*/
class RematCandidateSelector: public DataFlowAnalyzer
{
public:
explicit RematCandidateSelector(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {}
/// @returns a map from rematerialisation costs to a vector of variables to rematerialise
/// @returns a map from function name to rematerialisation costs to a vector of variables to rematerialise
/// and variables that occur in their expression.
/// While the map is sorted by cost, the contained vectors are sorted by the order of occurrence.
map<size_t, vector<tuple<YulString, set<YulString>>>> candidates()
map<YulString, map<size_t, vector<YulString>>> candidates()
{
map<size_t, vector<tuple<YulString, set<YulString>>>> cand;
for (auto const& candidate: m_candidates)
map<YulString, map<size_t, vector<YulString>>> cand;
for (auto const& [functionName, candidate]: m_candidates)
{
if (size_t const* cost = util::valueOrNullptr(m_expressionCodeCost, candidate))
{
size_t numRef = m_numReferences[candidate];
set<YulString> const* ref = references(candidate);
cand[*cost * numRef].emplace_back(candidate, ref ? move(*ref) : set<YulString>{});
cand[functionName][*cost * numRef].emplace_back(candidate);
}
}
return cand;
}
using DataFlowAnalyzer::operator();
void operator()(FunctionDefinition& _function) override
{
yulAssert(m_currentFunctionName.empty());
m_currentFunctionName = _function.name;
DataFlowAnalyzer::operator()(_function);
m_currentFunctionName = {};
}
void operator()(VariableDeclaration& _varDecl) override
{
DataFlowAnalyzer::operator()(_varDecl);
@ -84,7 +93,7 @@ public:
if (AssignedValue const* value = variableValue(varName))
{
yulAssert(!m_expressionCodeCost.count(varName), "");
m_candidates.emplace_back(varName);
m_candidates.emplace_back(m_currentFunctionName, varName);
m_expressionCodeCost[varName] = CodeCost::codeCost(m_dialect, *value->value);
}
}
@ -122,8 +131,10 @@ public:
m_expressionCodeCost.erase(_variable);
}
/// All candidate variables in order of occurrence.
vector<YulString> m_candidates;
YulString m_currentFunctionName = {};
/// All candidate variables by function name, in order of occurrence.
vector<pair<YulString, YulString>> m_candidates;
/// Candidate variables and the code cost of their value.
map<YulString, size_t> m_expressionCodeCost;
/// Number of references to each candidate variable.
@ -132,86 +143,95 @@ public:
/// Selects at most @a _numVariables among @a _candidates.
set<YulString> chooseVarsToEliminate(
map<size_t, vector<tuple<YulString, set<YulString>>>> const& _candidates,
map<size_t, vector<YulString>> const& _candidates,
size_t _numVariables
)
{
set<YulString> varsToEliminate;
for (auto&& [cost, candidates]: _candidates)
for (auto&& [candidate, references]: candidates)
for (auto&& candidate: candidates)
{
if (varsToEliminate.size() >= _numVariables)
return varsToEliminate;
// If a variable we would like to eliminate references another one
// we already selected for elimination, then stop selecting
// candidates. If we would add that variable, then the cost calculation
// for the previous variable would be off. Furthermore, we
// do not skip the variable because it would be better to properly re-compute
// the costs of all other variables instead.
for (YulString const& referencedVar: references)
if (varsToEliminate.count(referencedVar))
return varsToEliminate;
varsToEliminate.insert(candidate);
}
return varsToEliminate;
}
template <typename ASTNode>
void eliminateVariables(
Dialect const& _dialect,
ASTNode& _node,
size_t _numVariables,
Block& _ast,
map<YulString, int> const& _numVariables,
bool _allowMSizeOptimization
)
{
RematCandidateSelector selector{_dialect};
selector(_node);
Rematerialiser::run(_dialect, _node, chooseVarsToEliminate(selector.candidates(), _numVariables));
UnusedPruner::runUntilStabilised(_dialect, _node, _allowMSizeOptimization);
selector(_ast);
map<YulString, map<size_t, vector<YulString>>> candidates = selector.candidates();
set<YulString> varsToEliminate;
for (auto const& [functionName, numVariables]: _numVariables)
{
yulAssert(numVariables > 0);
varsToEliminate += chooseVarsToEliminate(candidates[functionName], static_cast<size_t>(numVariables));
}
Rematerialiser::run(_dialect, _ast, move(varsToEliminate));
// Do not remove functions.
set<YulString> allFunctions = NameCollector{_ast, NameCollector::OnlyFunctions}.names();
UnusedPruner::runUntilStabilised(_dialect, _ast, _allowMSizeOptimization, nullptr, allFunctions);
}
void eliminateVariables(
void eliminateVariablesOptimizedCodegen(
Dialect const& _dialect,
Block& _block,
vector<StackLayoutGenerator::StackTooDeep> const& _unreachables,
Block& _ast,
map<YulString, vector<StackLayoutGenerator::StackTooDeep>> const& _unreachables,
bool _allowMSizeOptimization
)
{
if (std::all_of(_unreachables.begin(), _unreachables.end(), [](auto const& _item) { return _item.second.empty(); }))
return;
RematCandidateSelector selector{_dialect};
selector(_block);
std::map<YulString, size_t> candidates;
for (auto [cost, candidatesWithCost]: selector.candidates())
for (auto candidate: candidatesWithCost)
candidates[get<0>(candidate)] = cost;
selector(_ast);
map<YulString, size_t> candidates;
for (auto const& [functionName, candidatesInFunction]: selector.candidates())
for (auto [cost, candidatesWithCost]: candidatesInFunction)
for (auto candidate: candidatesWithCost)
candidates[candidate] = cost;
set<YulString> varsToEliminate;
// TODO: this currently ignores the fact that variables may reference other variables we want to eliminate.
for (auto const& unreachable: _unreachables)
{
map<size_t, vector<YulString>> suitableCandidates;
size_t neededSlots = unreachable.deficit;
for (auto varName: unreachable.variableChoices)
for (auto const& [functionName, unreachables]: _unreachables)
for (auto const& unreachable: unreachables)
{
if (varsToEliminate.count(varName))
--neededSlots;
else if (size_t* cost = util::valueOrNullptr(candidates, varName))
if (!util::contains(suitableCandidates[*cost], varName))
suitableCandidates[*cost].emplace_back(varName);
}
for (auto candidatesByCost: suitableCandidates)
{
for (auto candidate: candidatesByCost.second)
if (neededSlots--)
varsToEliminate.emplace(candidate);
else
map<size_t, vector<YulString>> suitableCandidates;
size_t neededSlots = unreachable.deficit;
for (auto varName: unreachable.variableChoices)
{
if (varsToEliminate.count(varName))
--neededSlots;
else if (size_t* cost = util::valueOrNullptr(candidates, varName))
if (!util::contains(suitableCandidates[*cost], varName))
suitableCandidates[*cost].emplace_back(varName);
}
for (auto candidatesByCost: suitableCandidates)
{
for (auto candidate: candidatesByCost.second)
if (neededSlots--)
varsToEliminate.emplace(candidate);
else
break;
if (!neededSlots)
break;
if (!neededSlots)
break;
}
}
}
Rematerialiser::run(_dialect, _block, std::move(varsToEliminate), true);
UnusedPruner::runUntilStabilised(_dialect, _block, _allowMSizeOptimization);
Rematerialiser::run(_dialect, _ast, std::move(varsToEliminate), true);
// Do not remove functions.
set<YulString> allFunctions = NameCollector{_ast, NameCollector::OnlyFunctions}.names();
UnusedPruner::runUntilStabilised(_dialect, _ast, _allowMSizeOptimization, nullptr, allFunctions);
}
}
@ -239,21 +259,12 @@ bool StackCompressor::run(
{
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code);
Block& mainBlock = std::get<Block>(_object.code->statements.at(0));
if (
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, YulString{});
!stackTooDeepErrors.empty()
)
eliminateVariables(_dialect, mainBlock, stackTooDeepErrors, allowMSizeOptimzation);
for (size_t i = 1; i < _object.code->statements.size(); ++i)
{
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
if (
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, fun.name);
!stackTooDeepErrors.empty()
)
eliminateVariables(_dialect, fun.body, stackTooDeepErrors, allowMSizeOptimzation);
}
eliminateVariablesOptimizedCodegen(
_dialect,
*_object.code,
StackLayoutGenerator::reportStackTooDeep(*cfg),
allowMSizeOptimzation
);
}
else
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
@ -261,32 +272,12 @@ bool StackCompressor::run(
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
if (stackSurplus.empty())
return true;
if (stackSurplus.count(YulString{}))
{
yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value.");
eliminateVariables(
_dialect,
std::get<Block>(_object.code->statements.at(0)),
static_cast<size_t>(stackSurplus.at({})),
allowMSizeOptimzation
);
}
for (size_t i = 1; i < _object.code->statements.size(); ++i)
{
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
if (!stackSurplus.count(fun.name))
continue;
yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value.");
eliminateVariables(
_dialect,
fun,
static_cast<size_t>(stackSurplus.at(fun.name)),
allowMSizeOptimzation
);
}
eliminateVariables(
_dialect,
*_object.code,
stackSurplus,
allowMSizeOptimzation
);
}
return false;
}

View File

@ -93,26 +93,31 @@ void StructuralSimplifier::operator()(Block& _block)
void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
{
util::GenericVisitor visitor{
util::VisitorFallback<OptionalStatements>{},
[&](If& _ifStmt) -> OptionalStatements {
if (expressionAlwaysTrue(*_ifStmt.condition))
return {std::move(_ifStmt.body.statements)};
else if (expressionAlwaysFalse(*_ifStmt.condition))
return {vector<Statement>{}};
return {};
},
[&](Switch& _switchStmt) -> OptionalStatements {
if (std::optional<u256> const constExprVal = hasLiteralValue(*_switchStmt.expression))
return replaceConstArgSwitch(_switchStmt, constExprVal.value());
return {};
},
[&](ForLoop& _forLoop) -> OptionalStatements {
if (expressionAlwaysFalse(*_forLoop.condition))
return {std::move(_forLoop.pre.statements)};
return {};
}
// Explicit local variables ifLambda, switchLambda, forLoopLambda are created to avoid MSVC C++17 Debug test crash
// (Run-Time Check Failure #2 - Stack around the variable '....' was corrupted).
// As soon as the issue is fixed, this workaround can be removed.
auto ifLambda = [&](If& _ifStmt) -> OptionalStatements
{
if (expressionAlwaysTrue(*_ifStmt.condition))
return {std::move(_ifStmt.body.statements)};
else if (expressionAlwaysFalse(*_ifStmt.condition))
return {vector<Statement>{}};
return {};
};
auto switchLambda = [&](Switch& _switchStmt) -> OptionalStatements
{
if (std::optional<u256> const constExprVal = hasLiteralValue(*_switchStmt.expression))
return replaceConstArgSwitch(_switchStmt, constExprVal.value());
return {};
};
auto forLoopLambda = [&](ForLoop& _forLoop) -> OptionalStatements
{
if (expressionAlwaysFalse(*_forLoop.condition))
return {std::move(_forLoop.pre.statements)};
return {};
};
util::GenericVisitor visitor{util::VisitorFallback<OptionalStatements>{}, ifLambda, switchLambda, forLoopLambda};
util::iterateReplacing(
_statements,

View File

@ -246,3 +246,12 @@ function first_word
echo "$words" | cut -d " " -f 1
}
# Function reads from stdin. Therefore it has to be used with pipes.
function split_on_empty_lines_into_numbered_files
{
path_prefix="${1}"
path_suffix="${2}"
awk -v RS= "{print > (\"${path_prefix}_\"NR \"${path_suffix}\")}"
}

View File

@ -0,0 +1,453 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from textwrap import dedent
from typing import Any, Mapping, Optional, Set, Sequence, Union
import json
import sys
class DiffMode(Enum):
IN_PLACE = 'inplace'
TABLE = 'table'
class DifferenceStyle(Enum):
ABSOLUTE = 'absolute'
RELATIVE = 'relative'
HUMANIZED = 'humanized'
class OutputFormat(Enum):
JSON = 'json'
CONSOLE = 'console'
MARKDOWN = 'markdown'
DEFAULT_RELATIVE_PRECISION = 4
DEFAULT_DIFFERENCE_STYLE = {
DiffMode.IN_PLACE: DifferenceStyle.ABSOLUTE,
DiffMode.TABLE: DifferenceStyle.HUMANIZED,
}
assert all(t in DiffMode for t in DEFAULT_DIFFERENCE_STYLE)
assert all(d in DifferenceStyle for d in DEFAULT_DIFFERENCE_STYLE.values())
DEFAULT_OUTPUT_FORMAT = {
DiffMode.IN_PLACE: OutputFormat.JSON,
DiffMode.TABLE: OutputFormat.CONSOLE,
}
assert all(m in DiffMode for m in DEFAULT_OUTPUT_FORMAT)
assert all(o in OutputFormat for o in DEFAULT_OUTPUT_FORMAT.values())
class ValidationError(Exception):
pass
class CommandLineError(ValidationError):
pass
class BenchmarkDiffer:
difference_style: DifferenceStyle
relative_precision: Optional[int]
output_format: OutputFormat
def __init__(
self,
difference_style: DifferenceStyle,
relative_precision: Optional[int],
output_format: OutputFormat,
):
self.difference_style = difference_style
self.relative_precision = relative_precision
self.output_format = output_format
def run(self, before: Any, after: Any) -> Optional[Union[dict, str, int, float]]:
if not isinstance(before, dict) or not isinstance(after, dict):
return self._diff_scalars(before, after)
if before.get('version') != after.get('version'):
return self._humanize_diff('!V')
diff = {}
for key in (set(before) | set(after)) - {'version'}:
value_diff = self.run(before.get(key), after.get(key))
if value_diff not in [None, {}]:
diff[key] = value_diff
return diff
def _diff_scalars(self, before: Any, after: Any) -> Optional[Union[str, int, float, dict]]:
assert not isinstance(before, dict) or not isinstance(after, dict)
if before is None and after is None:
return {}
if before is None:
return self._humanize_diff('!B')
if after is None:
return self._humanize_diff('!A')
if not isinstance(before, (int, float)) or not isinstance(after, (int, float)):
return self._humanize_diff('!T')
number_diff = self._diff_numbers(before, after)
if self.difference_style != DifferenceStyle.HUMANIZED:
return number_diff
return self._humanize_diff(number_diff)
def _diff_numbers(self, value_before: Union[int, float], value_after: Union[int, float]) -> Union[str, int, float]:
diff: Union[str, int, float]
if self.difference_style == DifferenceStyle.ABSOLUTE:
diff = value_after - value_before
if isinstance(diff, float) and diff.is_integer():
diff = int(diff)
return diff
if value_before == 0:
if value_after > 0:
return '+INF'
elif value_after < 0:
return '-INF'
else:
return 0
diff = (value_after - value_before) / abs(value_before)
if self.relative_precision is not None:
rounded_diff = round(diff, self.relative_precision)
if rounded_diff == 0 and diff < 0:
diff = '-0'
elif rounded_diff == 0 and diff > 0:
diff = '+0'
else:
diff = rounded_diff
if isinstance(diff, float) and diff.is_integer():
diff = int(diff)
return diff
def _humanize_diff(self, diff: Union[str, int, float]) -> str:
def wrap(value: str, symbol: str):
return f"{symbol}{value}{symbol}"
markdown = (self.output_format == OutputFormat.MARKDOWN)
if isinstance(diff, str) and diff.startswith('!'):
return wrap(diff, '`' if markdown else '')
value: Union[str, int, float]
if isinstance(diff, (int, float)):
value = diff * 100
if isinstance(value, float) and self.relative_precision is not None:
# The multiplication can result in new significant digits appearing. We need to reround.
# NOTE: round() works fine with negative precision.
value = round(value, self.relative_precision - 2)
if isinstance(value, float) and value.is_integer():
value = int(value)
suffix = ''
prefix = ''
if diff < 0:
prefix = ''
if markdown:
suffix += ''
elif diff > 0:
prefix = '+'
if markdown:
suffix += ''
important = (diff != 0)
else:
value = diff
important = False
prefix = ''
suffix = ''
return wrap(
wrap(
f"{prefix}{value}%{suffix}",
'`' if markdown else ''
),
'**' if important and markdown else ''
)
@dataclass(frozen=True)
class DiffTable:
columns: Mapping[str, Sequence[Union[int, float, str]]]
class DiffTableSet:
table_headers: Sequence[str]
row_headers: Sequence[str]
column_headers: Sequence[str]
# Cells is a nested dict rather than a 3D array so that conversion to JSON is straightforward
cells: Mapping[str, Mapping[str, Mapping[str, Union[int, float, str]]]] # preset -> project -> attribute
def __init__(self, diff: dict):
self.table_headers = sorted(self._find_all_preset_names(diff))
self.column_headers = sorted(self._find_all_attribute_names(diff))
self.row_headers = sorted(project for project in diff)
# All dimensions must have unique values
assert len(self.table_headers) == len(set(self.table_headers))
assert len(self.column_headers) == len(set(self.column_headers))
assert len(self.row_headers) == len(set(self.row_headers))
self.cells = {
preset: {
project: {
attribute: self._cell_content(diff, project, preset, attribute)
for attribute in self.column_headers
}
for project in self.row_headers
}
for preset in self.table_headers
}
def calculate_row_column_width(self) -> int:
return max(len(h) for h in self.row_headers)
def calculate_column_widths(self, table_header: str) -> Sequence[int]:
assert table_header in self.table_headers
return [
max(
len(column_header),
max(
len(str(self.cells[table_header][row_header][column_header]))
for row_header in self.row_headers
)
)
for column_header in self.column_headers
]
@classmethod
def _find_all_preset_names(cls, diff: dict) -> Set[str]:
return {
preset
for project, project_diff in diff.items()
if isinstance(project_diff, dict)
for preset in project_diff
}
@classmethod
def _find_all_attribute_names(cls, diff: dict) -> Set[str]:
return {
attribute
for project, project_diff in diff.items()
if isinstance(project_diff, dict)
for preset, preset_diff in project_diff.items()
if isinstance(preset_diff, dict)
for attribute in preset_diff
}
@classmethod
def _cell_content(cls, diff: dict, project: str, preset: str, attribute: str) -> str:
assert project in diff
if isinstance(diff[project], str):
return diff[project]
if preset not in diff[project]:
return ''
if isinstance(diff[project][preset], str):
return diff[project][preset]
if attribute not in diff[project][preset]:
return ''
return diff[project][preset][attribute]
class DiffTableFormatter:
LEGEND = dedent("""
`!V` = version mismatch
`!B` = no value in the "before" version
`!A` = no value in the "after" version
`!T` = one or both values were not numeric and could not be compared
`-0` = very small negative value rounded to zero
`+0` = very small positive value rounded to zero
""")
@classmethod
def run(cls, diff_table_set: DiffTableSet, output_format: OutputFormat):
if output_format == OutputFormat.JSON:
return json.dumps(diff_table_set.cells, indent=4, sort_keys=True)
else:
assert output_format in {OutputFormat.CONSOLE, OutputFormat.MARKDOWN}
output = ''
for table_header in diff_table_set.table_headers:
column_widths = ([
diff_table_set.calculate_row_column_width(),
*diff_table_set.calculate_column_widths(table_header)
])
if output_format == OutputFormat.MARKDOWN:
output += f'\n### `{table_header}`\n'
else:
output += f'\n{table_header.upper()}\n'
if output_format == OutputFormat.CONSOLE:
output += cls._format_separator_row(column_widths, output_format) + '\n'
output += cls._format_data_row(['project', *diff_table_set.column_headers], column_widths) + '\n'
output += cls._format_separator_row(column_widths, output_format) + '\n'
for row_header in diff_table_set.row_headers:
row = [
diff_table_set.cells[table_header][row_header][column_header]
for column_header in diff_table_set.column_headers
]
output += cls._format_data_row([row_header, *row], column_widths) + '\n'
if output_format == OutputFormat.CONSOLE:
output += cls._format_separator_row(column_widths, output_format) + '\n'
if output_format == OutputFormat.MARKDOWN:
output += f'\n{cls.LEGEND}\n'
return output
@classmethod
def _format_separator_row(cls, widths: Sequence[int], output_format: OutputFormat):
assert output_format in {OutputFormat.CONSOLE, OutputFormat.MARKDOWN}
if output_format == OutputFormat.MARKDOWN:
return '|:' + ':|-'.join('-' * width for width in widths) + ':|'
else:
return '|-' + '-|-'.join('-' * width for width in widths) + '-|'
@classmethod
def _format_data_row(cls, cells: Sequence[Union[int, float, str]], widths: Sequence[int]):
assert len(cells) == len(widths)
return '| ' + ' | '.join(str(cell).rjust(width) for cell, width in zip(cells, widths)) + ' |'
@dataclass(frozen=True)
class CommandLineOptions:
diff_mode: DiffMode
report_before: Path
report_after: Path
difference_style: DifferenceStyle
relative_precision: int
output_format: OutputFormat
def process_commandline() -> CommandLineOptions:
script_description = (
"Compares summarized benchmark reports and outputs JSON with the same structure but listing only differences. "
"Can also print the output as markdown table and format the values to make differences stand out more."
)
parser = ArgumentParser(description=script_description)
parser.add_argument(
dest='diff_mode',
choices=[m.value for m in DiffMode],
help=(
"Diff mode: "
f"'{DiffMode.IN_PLACE.value}' preserves input JSON structure and replace values with differences; "
f"'{DiffMode.TABLE.value}' creates a table assuming 3-level project/preset/attribute structure."
)
)
parser.add_argument(dest='report_before', help="Path to a JSON file containing original benchmark results.")
parser.add_argument(dest='report_after', help="Path to a JSON file containing new benchmark results.")
parser.add_argument(
'--style',
dest='difference_style',
choices=[s.value for s in DifferenceStyle],
help=(
"How to present numeric differences: "
f"'{DifferenceStyle.ABSOLUTE.value}' subtracts new from original; "
f"'{DifferenceStyle.RELATIVE.value}' also divides by the original; "
f"'{DifferenceStyle.HUMANIZED.value}' is like relative but value is a percentage and "
"positive/negative changes are emphasized. "
f"(default: '{DEFAULT_DIFFERENCE_STYLE[DiffMode.IN_PLACE]}' in '{DiffMode.IN_PLACE.value}' mode, "
f"'{DEFAULT_DIFFERENCE_STYLE[DiffMode.TABLE]}' in '{DiffMode.TABLE.value}' mode)"
)
)
# NOTE: Negative values are valid for precision. round() handles them in a sensible way.
parser.add_argument(
'--precision',
dest='relative_precision',
type=int,
default=DEFAULT_RELATIVE_PRECISION,
help=(
"Number of significant digits for relative differences. "
f"Note that with --style={DifferenceStyle.HUMANIZED.value} the rounding is applied "
"**before** converting the value to a percentage so you need to add 2. "
f"Has no effect when used together with --style={DifferenceStyle.ABSOLUTE.value}. "
f"(default: {DEFAULT_RELATIVE_PRECISION})"
)
)
parser.add_argument(
'--output-format',
dest='output_format',
choices=[o.value for o in OutputFormat],
help=(
"The format to use for the diff: "
f"'{OutputFormat.JSON.value}' is raw JSON; "
f"'{OutputFormat.CONSOLE.value}' is a table with human-readable values that will look good in the console output. "
f"'{OutputFormat.MARKDOWN.value}' is similar '{OutputFormat.CONSOLE.value}' but adjusted to "
"render as proper markdown and with extra elements (legend, emoji to make non-zero values stand out more, etc)."
f"(default: '{DEFAULT_OUTPUT_FORMAT[DiffMode.IN_PLACE]}' in '{DiffMode.IN_PLACE.value}' mode, "
f"'{DEFAULT_OUTPUT_FORMAT[DiffMode.TABLE]}' in '{DiffMode.TABLE.value}' mode)"
)
)
options = parser.parse_args()
if options.difference_style is not None:
difference_style = DifferenceStyle(options.difference_style)
else:
difference_style = DEFAULT_DIFFERENCE_STYLE[DiffMode(options.diff_mode)]
if options.output_format is not None:
output_format = OutputFormat(options.output_format)
else:
output_format = DEFAULT_OUTPUT_FORMAT[DiffMode(options.diff_mode)]
processed_options = CommandLineOptions(
diff_mode=DiffMode(options.diff_mode),
report_before=Path(options.report_before),
report_after=Path(options.report_after),
difference_style=difference_style,
relative_precision=options.relative_precision,
output_format=output_format,
)
if processed_options.diff_mode == DiffMode.IN_PLACE and processed_options.output_format != OutputFormat.JSON:
raise CommandLineError(
f"Only the '{OutputFormat.JSON.value}' output format is supported in the '{DiffMode.IN_PLACE.value}' mode."
)
return processed_options
def main():
try:
options = process_commandline()
differ = BenchmarkDiffer(options.difference_style, options.relative_precision, options.output_format)
diff = differ.run(
json.loads(options.report_before.read_text('utf-8')),
json.loads(options.report_after.read_text('utf-8')),
)
if options.diff_mode == DiffMode.IN_PLACE:
print(json.dumps(diff, indent=4, sort_keys=True))
else:
assert options.diff_mode == DiffMode.TABLE
print(DiffTableFormatter.run(DiffTableSet(diff), options.output_format))
return 0
except CommandLineError as exception:
print(f"ERROR: {exception}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@ -41,7 +41,7 @@
#include <libsolidity/lsp/LanguageServer.h>
#include <libsolidity/lsp/Transport.h>
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/Disassemble.h>
@ -317,7 +317,7 @@ void CommandLineInterface::handleABI(string const& _contract)
if (!m_options.compiler.outputs.abi)
return;
string data = jsonCompactPrint(removeNullMembers(m_compiler->contractABI(_contract)));
string data = jsonPrint(removeNullMembers(m_compiler->contractABI(_contract)), m_options.formatting.json);
if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + ".abi", data);
else
@ -331,7 +331,7 @@ void CommandLineInterface::handleStorageLayout(string const& _contract)
if (!m_options.compiler.outputs.storageLayout)
return;
string data = jsonCompactPrint(removeNullMembers(m_compiler->storageLayout(_contract)));
string data = jsonPrint(removeNullMembers(m_compiler->storageLayout(_contract)), m_options.formatting.json);
if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contract) + "_storage.json", data);
else
@ -361,12 +361,13 @@ void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contra
if (enabled)
{
std::string output = jsonPrettyPrint(
std::string output = jsonPrint(
removeNullMembers(
_natspecDev ?
m_compiler->natspecDev(_contract) :
m_compiler->natspecUser(_contract)
)
),
m_options.formatting.json
);
if (!m_options.output.dir.empty())
@ -892,7 +893,7 @@ void CommandLineInterface::handleAst()
{
stringstream data;
string postfix = "";
ASTJsonConverter(m_compiler->state(), m_compiler->sourceIndices()).print(data, m_compiler->ast(sourceCode.first));
ASTJsonConverter(m_compiler->state(), m_compiler->sourceIndices()).print(data, m_compiler->ast(sourceCode.first), m_options.formatting.json);
postfix += "_json";
boost::filesystem::path path(sourceCode.first);
createFile(path.filename().string() + postfix + ".ast", data.str());
@ -904,7 +905,7 @@ void CommandLineInterface::handleAst()
for (auto const& sourceCode: m_fileReader.sourceUnits())
{
sout() << endl << "======= " << sourceCode.first << " =======" << endl;
ASTJsonConverter(m_compiler->state(), m_compiler->sourceIndices()).print(sout(), m_compiler->ast(sourceCode.first));
ASTJsonConverter(m_compiler->state(), m_compiler->sourceIndices()).print(sout(), m_compiler->ast(sourceCode.first), m_options.formatting.json);
}
}
}
@ -1013,18 +1014,18 @@ string CommandLineInterface::objectWithLinkRefsHex(evmasm::LinkerObject const& _
return out;
}
void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine)
void CommandLineInterface::assemble(yul::YulStack::Language _language, yul::YulStack::Machine _targetMachine)
{
solAssert(m_options.input.mode == InputMode::Assembler, "");
bool successful = true;
map<string, yul::AssemblyStack> assemblyStacks;
map<string, yul::YulStack> yulStacks;
for (auto const& src: m_fileReader.sourceUnits())
{
// --no-optimize-yul option is not accepted in assembly mode.
solAssert(!m_options.optimizer.noOptimizeYul, "");
auto& stack = assemblyStacks[src.first] = yul::AssemblyStack(
auto& stack = yulStacks[src.first] = yul::YulStack(
m_options.output.evmVersion,
_language,
m_options.optimiserSettings(),
@ -1039,7 +1040,7 @@ void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
stack.optimize();
}
for (auto const& sourceAndStack: assemblyStacks)
for (auto const& sourceAndStack: yulStacks)
{
auto const& stack = sourceAndStack.second;
SourceReferenceFormatter formatter(serr(false), stack, coloredOutput(m_options), m_options.formatting.withErrorIds);
@ -1062,11 +1063,11 @@ void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
for (auto const& src: m_fileReader.sourceUnits())
{
string machine =
_targetMachine == yul::AssemblyStack::Machine::EVM ? "EVM" :
_targetMachine == yul::YulStack::Machine::EVM ? "EVM" :
"Ewasm";
sout() << endl << "======= " << src.first << " (" << machine << ") =======" << endl;
yul::AssemblyStack& stack = assemblyStacks[src.first];
yul::YulStack& stack = yulStacks[src.first];
if (m_options.compiler.outputs.irOptimized)
{
@ -1076,9 +1077,9 @@ void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
sout() << stack.print() << endl;
}
if (_language != yul::AssemblyStack::Language::Ewasm && _targetMachine == yul::AssemblyStack::Machine::Ewasm)
if (_language != yul::YulStack::Language::Ewasm && _targetMachine == yul::YulStack::Machine::Ewasm)
{
stack.translate(yul::AssemblyStack::Language::Ewasm);
stack.translate(yul::YulStack::Language::Ewasm);
stack.optimize();
if (m_options.compiler.outputs.ewasmIR)
@ -1102,10 +1103,10 @@ void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
serr() << "No binary representation found." << endl;
}
solAssert(_targetMachine == yul::AssemblyStack::Machine::Ewasm || _targetMachine == yul::AssemblyStack::Machine::EVM, "");
solAssert(_targetMachine == yul::YulStack::Machine::Ewasm || _targetMachine == yul::YulStack::Machine::EVM, "");
if (
(_targetMachine == yul::AssemblyStack::Machine::EVM && m_options.compiler.outputs.asm_) ||
(_targetMachine == yul::AssemblyStack::Machine::Ewasm && m_options.compiler.outputs.ewasm)
(_targetMachine == yul::YulStack::Machine::EVM && m_options.compiler.outputs.asm_) ||
(_targetMachine == yul::YulStack::Machine::Ewasm && m_options.compiler.outputs.ewasm)
)
{
sout() << endl << "Text representation:" << endl;
@ -1149,7 +1150,7 @@ void CommandLineInterface::outputCompilationResults()
{
string ret;
if (m_options.compiler.outputs.asmJson)
ret = jsonPrettyPrint(removeNullMembers(m_compiler->assemblyJSON(contract)));
ret = util::jsonPrint(removeNullMembers(m_compiler->assemblyJSON(contract)), m_options.formatting.json);
else
ret = m_compiler->assemblyString(contract, m_fileReader.sourceUnits());

View File

@ -27,7 +27,7 @@
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/FileReader.h>
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <iostream>
#include <memory>
@ -90,7 +90,7 @@ private:
/// @returns the full object with library placeholder hints in hex.
static std::string objectWithLinkRefsHex(evmasm::LinkerObject const& _obj);
void assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine);
void assemble(yul::YulStack::Language _language, yul::YulStack::Machine _targetMachine);
void outputCompilationResults();

View File

@ -1109,8 +1109,8 @@ void CommandLineParser::processArgs()
}
// switch to assembly mode
using Input = yul::AssemblyStack::Language;
using Machine = yul::AssemblyStack::Machine;
using Input = yul::YulStack::Language;
using Machine = yul::YulStack::Machine;
m_options.assembly.inputLanguage = m_args.count(g_strYul) ? Input::Yul : (m_args.count(g_strStrictAssembly) ? Input::StrictAssembly : Input::Assembly);
if (m_args.count(g_strMachine))

View File

@ -25,7 +25,7 @@
#include <libsolidity/interface/FileReader.h>
#include <libsolidity/interface/ImportRemapper.h>
#include <libyul/AssemblyStack.h>
#include <libyul/YulStack.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/EVMVersion.h>
@ -190,8 +190,8 @@ struct CommandLineOptions
struct
{
yul::AssemblyStack::Machine targetMachine = yul::AssemblyStack::Machine::EVM;
yul::AssemblyStack::Language inputLanguage = yul::AssemblyStack::Language::StrictAssembly;
yul::YulStack::Machine targetMachine = yul::YulStack::Machine::EVM;
yul::YulStack::Language inputLanguage = yul::YulStack::Language::StrictAssembly;
} assembly;
struct

View File

@ -159,26 +159,33 @@ bool CommonOptions::parse(int argc, char const* const* argv)
po::variables_map arguments;
addOptions();
po::command_line_parser cmdLineParser(argc, argv);
cmdLineParser.options(options);
auto parsedOptions = cmdLineParser.run();
po::store(parsedOptions, arguments);
po::notify(arguments);
try
{
po::command_line_parser cmdLineParser(argc, argv);
cmdLineParser.options(options);
auto parsedOptions = cmdLineParser.run();
po::store(parsedOptions, arguments);
po::notify(arguments);
for (auto const& parsedOption: parsedOptions.options)
if (parsedOption.position_key >= 0)
{
if (
parsedOption.original_tokens.empty() ||
(parsedOption.original_tokens.size() == 1 && parsedOption.original_tokens.front().empty())
)
continue; // ignore empty options
std::stringstream errorMessage;
errorMessage << "Unrecognized option: ";
for (auto const& token: parsedOption.original_tokens)
errorMessage << token;
BOOST_THROW_EXCEPTION(std::runtime_error(errorMessage.str()));
}
for (auto const& parsedOption: parsedOptions.options)
if (parsedOption.position_key >= 0)
{
if (
parsedOption.original_tokens.empty() ||
(parsedOption.original_tokens.size() == 1 && parsedOption.original_tokens.front().empty())
)
continue; // ignore empty options
std::stringstream errorMessage;
errorMessage << "Unrecognized option: ";
for (auto const& token: parsedOption.original_tokens)
errorMessage << token;
BOOST_THROW_EXCEPTION(std::runtime_error(errorMessage.str()));
}
}
catch (po::error const& exception)
{
solThrow(ConfigException, exception.what());
}
if (vmPaths.empty())
{

View File

@ -75,6 +75,9 @@ struct CommonOptions
langutil::EVMVersion evmVersion() const;
virtual void addOptions();
// @returns true if the program should continue, false if it should exit immediately without
// reporting an error.
// Throws ConfigException or std::runtime_error if parsing fails.
virtual bool parse(int argc, char const* const* argv);
// Throws a ConfigException on error
virtual void validate() const;

View File

@ -204,88 +204,106 @@ int registerTests(
return numTestsAdded;
}
void initializeOptions()
bool initializeOptions()
{
auto const& suite = boost::unit_test::framework::master_test_suite();
auto options = std::make_unique<solidity::test::CommonOptions>();
solAssert(options->parse(suite.argc, suite.argv), "Failed to parse options!");
bool shouldContinue = options->parse(suite.argc, suite.argv);
if (!shouldContinue)
return false;
options->validate();
solidity::test::CommonOptions::setSingleton(std::move(options));
return true;
}
}
// TODO: Prototype -- why isn't this declared in the boost headers?
// TODO: replace this with a (global) fixture.
test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] );
test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]);
test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[])
{
using namespace solidity::test;
master_test_suite_t& master = framework::master_test_suite();
master.p_name.value = "SolidityTests";
initializeOptions();
if (!solidity::test::loadVMs(solidity::test::CommonOptions::get()))
exit(1);
if (solidity::test::CommonOptions::get().disableSemanticTests)
cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl;
if (!solidity::test::CommonOptions::get().enforceGasTest)
cout << endl << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl;
Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches);
if (CommonOptions::get().batches > 1)
cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl;
// Batch the boost tests
BoostBatcher boostBatcher(batcher);
traverse_test_tree(master, boostBatcher, true);
// Include the interactive tests in the automatic tests as well
for (auto const& ts: g_interactiveTestsuites)
try
{
auto const& options = solidity::test::CommonOptions::get();
bool shouldContinue = initializeOptions();
if (!shouldContinue)
exit(EXIT_SUCCESS);
if (ts.smt && options.disableSMT)
continue;
if (!solidity::test::loadVMs(solidity::test::CommonOptions::get()))
exit(EXIT_FAILURE);
if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests)
continue;
if (solidity::test::CommonOptions::get().disableSemanticTests)
cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl;
//TODO
//solAssert(
registerTests(
master,
options.testPath / ts.path,
ts.subpath,
options.enforceViaYul,
options.enforceCompileToEwasm,
ts.labels,
ts.testCaseCreator,
batcher
);
// > 0, std::string("no ") + ts.title + " tests found");
if (!solidity::test::CommonOptions::get().enforceGasTest)
cout << endl << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl;
Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches);
if (CommonOptions::get().batches > 1)
cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl;
// Batch the boost tests
BoostBatcher boostBatcher(batcher);
traverse_test_tree(master, boostBatcher, true);
// Include the interactive tests in the automatic tests as well
for (auto const& ts: g_interactiveTestsuites)
{
auto const& options = solidity::test::CommonOptions::get();
if (ts.smt && options.disableSMT)
continue;
if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests)
continue;
//TODO
//solAssert(
registerTests(
master,
options.testPath / ts.path,
ts.subpath,
options.enforceViaYul,
options.enforceCompileToEwasm,
ts.labels,
ts.testCaseCreator,
batcher
);
// > 0, std::string("no ") + ts.title + " tests found");
}
if (solidity::test::CommonOptions::get().disableSemanticTests)
{
for (auto suite: {
"ABIDecoderTest",
"ABIEncoderTest",
"SolidityAuctionRegistrar",
"SolidityWallet",
"GasMeterTests",
"GasCostTests",
"SolidityEndToEndTest",
"SolidityOptimizer"
})
removeTestSuite(suite);
}
}
if (solidity::test::CommonOptions::get().disableSemanticTests)
catch (solidity::test::ConfigException const& exception)
{
for (auto suite: {
"ABIDecoderTest",
"ABIEncoderTest",
"SolidityAuctionRegistrar",
"SolidityWallet",
"GasMeterTests",
"GasCostTests",
"SolidityEndToEndTest",
"SolidityOptimizer"
})
removeTestSuite(suite);
cerr << exception.what() << endl;
exit(EXIT_FAILURE);
}
catch (std::runtime_error const& exception)
{
cerr << exception.what() << endl;
exit(EXIT_FAILURE);
}
return nullptr;

View File

@ -310,6 +310,9 @@ function test_solc_assembly_output
function test_via_ir_equivalence()
{
SOLTMPDIR=$(mktemp -d)
pushd "$SOLTMPDIR"
(( $# <= 2 )) || fail "This function accepts at most two arguments."
if [[ $2 != --optimize ]] && [[ $2 != "" ]]
@ -317,50 +320,57 @@ function test_via_ir_equivalence()
fail "The second argument must be --optimize if present."
fi
local solidity_code="$1"
local solidity_file="$1"
local optimize_flag="$2"
output_file_prefix=$(basename "$1" .sol)
local optimizer_flags=()
[[ $optimize_flag == "" ]] || optimizer_flags+=("$optimize_flag")
[[ $optimize_flag == "" ]] || output_file_prefix+="_optimize"
local ir_output
ir_output=$(
echo "$solidity_code" |
msg_on_error --no-stderr "$SOLC" - --ir-optimized --debug-info location "${optimizer_flags[@]}" |
sed '/^Optimized IR:$/d'
)
msg_on_error --no-stderr "$SOLC" --ir-optimized --debug-info location "${optimizer_flags[@]}" "$solidity_file" |
sed '/^Optimized IR:$/d' |
split_on_empty_lines_into_numbered_files $output_file_prefix ".yul"
for yul_file in $(find . -name "${output_file_prefix}*.yul" | sort -V); do
msg_on_error --no-stderr "$SOLC" --strict-assembly --asm "${optimizer_flags[@]}" "$yul_file" |
sed '/^Text representation:$/d' > "${yul_file/.yul/.asm}"
done
local asm_output_two_stage asm_output_via_ir
asm_output_two_stage=$(
echo "$ir_output" |
msg_on_error --no-stderr "$SOLC" - --strict-assembly --asm "${optimizer_flags[@]}" |
sed '/^======= <stdin>/d' |
sed '/^Text representation:$/d'
)
for asm_file in $(find . -name "${output_file_prefix}*.asm" | sort -V); do
asm_output_two_stage+=$(sed '/^asm_output_two_stage:$/d' "$asm_file" | sed '/^=======/d')
done
asm_output_via_ir=$(
echo "$solidity_code" |
msg_on_error --no-stderr "$SOLC" - --via-ir --asm --debug-info location "${optimizer_flags[@]}" |
sed '/^======= <stdin>/d' |
sed '/^EVM assembly:$/d'
msg_on_error --no-stderr "$SOLC" --via-ir --asm --debug-info location "${optimizer_flags[@]}" "$solidity_file" |
sed '/^EVM assembly:$/d' |
sed '/^=======/d'
)
diff_values "$asm_output_two_stage" "$asm_output_via_ir" --ignore-space-change --ignore-blank-lines
local bin_output_two_stage bin_output_via_ir
bin_output_two_stage=$(
echo "$ir_output" |
msg_on_error --no-stderr "$SOLC" - --strict-assembly --bin "${optimizer_flags[@]}" |
sed '/^======= <stdin>/d' |
sed '/^Binary representation:$/d'
)
for yul_file in $(find . -name "${output_file_prefix}*.yul" | sort -V); do
bin_output_two_stage+=$(
msg_on_error --no-stderr "$SOLC" --strict-assembly --bin "${optimizer_flags[@]}" "$yul_file" |
sed '/^Binary representation:$/d' |
sed '/^=======/d'
)
done
bin_output_via_ir=$(
echo "$solidity_code" |
msg_on_error --no-stderr "$SOLC" - --via-ir --bin "${optimizer_flags[@]}" |
sed '/^======= <stdin>/d' |
sed '/^Binary:$/d'
msg_on_error --no-stderr "$SOLC" --via-ir --bin "${optimizer_flags[@]}" "$solidity_file" |
sed '/^Binary:$/d' |
sed '/^=======/d'
)
diff_values "$bin_output_two_stage" "$bin_output_via_ir" --ignore-space-change --ignore-blank-lines
popd
rm -r "$SOLTMPDIR"
}
## RUN
@ -590,20 +600,21 @@ printTask "Testing assemble, yul, strict-assembly and optimize..."
printTask "Testing the eqivalence of --via-ir and a two-stage compilation..."
(
printTask " - Smoke test"
test_via_ir_equivalence "contract C {}"
printTask " - Smoke test (optimized)"
test_via_ir_equivalence "contract C {}" --optimize
externalContracts=(
deposit_contract.sol
FixedFeeRegistrar.sol
_stringutils/stringutils.sol
externalTests/solc-js/DAO/TokenCreation.sol
libsolidity/semanticTests/externalContracts/_prbmath/PRBMathSD59x18.sol
libsolidity/semanticTests/externalContracts/_prbmath/PRBMathUD60x18.sol
libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol
libsolidity/semanticTests/externalContracts/deposit_contract.sol
libsolidity/semanticTests/externalContracts/FixedFeeRegistrar.sol
libsolidity/semanticTests/externalContracts/snark.sol
)
requiresOptimizer=(
deposit_contract.sol
FixedFeeRegistrar.sol
externalTests/solc-js/DAO/TokenCreation.sol
libsolidity/semanticTests/externalContracts/deposit_contract.sol
libsolidity/semanticTests/externalContracts/FixedFeeRegistrar.sol
libsolidity/semanticTests/externalContracts/snark.sol
)
for contractFile in "${externalContracts[@]}"
@ -611,11 +622,11 @@ printTask "Testing the eqivalence of --via-ir and a two-stage compilation..."
if ! [[ "${requiresOptimizer[*]}" =~ $contractFile ]]
then
printTask " - ${contractFile}"
test_via_ir_equivalence "$(cat "${REPO_ROOT}/test/libsolidity/semanticTests/externalContracts/${contractFile}")"
test_via_ir_equivalence "${REPO_ROOT}/test/${contractFile}"
fi
printTask " - ${contractFile} (optimized)"
test_via_ir_equivalence "$(cat "${REPO_ROOT}/test/libsolidity/semanticTests/externalContracts/${contractFile}")" --optimize
test_via_ir_equivalence "${REPO_ROOT}/test/${contractFile}" --optimize
done
)

View File

@ -1 +1 @@
--asm-json
--asm-json --pretty-json

View File

@ -450,9 +450,9 @@ EVM assembly:
{
"begin": 77,
"end": 158,
"jumpType": "[in]",
"name": "JUMP",
"source": 0,
"value": "[in]"
"source": 0
},
{
"begin": 77,
@ -477,9 +477,9 @@ EVM assembly:
{
"begin": 77,
"end": 158,
"jumpType": "[in]",
"name": "JUMP",
"source": 0,
"value": "[in]"
"source": 0
},
{
"begin": 77,
@ -555,9 +555,9 @@ EVM assembly:
{
"begin": 118,
"end": 125,
"jumpType": "[in]",
"name": "JUMP",
"source": 0,
"value": "[in]"
"source": 0
},
{
"begin": 118,
@ -657,9 +657,9 @@ EVM assembly:
{
"begin": 77,
"end": 158,
"jumpType": "[out]",
"name": "JUMP",
"source": 0,
"value": "[out]"
"source": 0
},
{
"begin": 88,
@ -752,9 +752,9 @@ EVM assembly:
{
"begin": 334,
"end": 411,
"jumpType": "[out]",
"name": "JUMP",
"source": 1,
"value": "[out]"
"source": 1
},
{
"begin": 417,
@ -792,9 +792,9 @@ EVM assembly:
{
"begin": 490,
"end": 514,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 490,
@ -875,9 +875,9 @@ EVM assembly:
{
"begin": 417,
"end": 539,
"jumpType": "[out]",
"name": "JUMP",
"source": 1,
"value": "[out]"
"source": 1
},
{
"begin": 545,
@ -946,9 +946,9 @@ EVM assembly:
{
"begin": 645,
"end": 678,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 645,
@ -990,9 +990,9 @@ EVM assembly:
{
"begin": 545,
"end": 684,
"jumpType": "[out]",
"name": "JUMP",
"source": 1,
"value": "[out]"
"source": 1
},
{
"begin": 690,
@ -1081,9 +1081,9 @@ EVM assembly:
{
"begin": 804,
"end": 883,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 804,
@ -1159,9 +1159,9 @@ EVM assembly:
{
"begin": 949,
"end": 1002,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 949,
@ -1221,9 +1221,9 @@ EVM assembly:
{
"begin": 690,
"end": 1019,
"jumpType": "[out]",
"name": "JUMP",
"source": 1,
"value": "[out]"
"source": 1
},
{
"begin": 1025,
@ -1341,9 +1341,9 @@ EVM assembly:
{
"begin": 1270,
"end": 1290,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 1270,
@ -1393,9 +1393,9 @@ EVM assembly:
{
"begin": 1304,
"end": 1324,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 1304,
@ -1489,9 +1489,9 @@ EVM assembly:
{
"begin": 1464,
"end": 1482,
"jumpType": "[in]",
"name": "JUMP",
"source": 1,
"value": "[in]"
"source": 1
},
{
"begin": 1464,
@ -1576,11 +1576,16 @@ EVM assembly:
{
"begin": 1211,
"end": 1516,
"jumpType": "[out]",
"name": "JUMP",
"source": 1,
"value": "[out]"
"source": 1
}
]
}
}
},
"sourceList":
[
"asm_json/input.sol",
"#utility.yul"
]
}

View File

@ -0,0 +1 @@
--asm-json

View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C {
function f(uint x) public pure {
x += 42;
require(x > 100);
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
--ast-compact-json --allow-paths .

View File

@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C {}

View File

@ -0,0 +1,5 @@
JSON AST (compact format):
======= ast_compact_json_no_pretty_json/input.sol =======
{"absolutePath":"ast_compact_json_no_pretty_json/input.sol","exportedSymbols":{"C":[2]},"id":3,"license":"GPL-3.0","nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity",">=","0.0"],"nodeType":"PragmaDirective","src":"36:22:0"},{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":2,"linearizedBaseContracts":[2],"name":"C","nameLocation":"69:1:0","nodeType":"ContractDefinition","nodes":[],"scope":3,"src":"60:13:0","usedErrors":[]}],"src":"36:38:0"}

View File

@ -1 +1 @@
--ast-compact-json --base-path . --allow-paths .
--ast-compact-json --pretty-json --base-path . --allow-paths .

View File

@ -1,7 +1,7 @@
{
"contracts":
{
"pretty_json_combined/input.sol:C":
"combined_json_abi/input.sol:C":
{
"abi": []
}

View File

@ -0,0 +1 @@
--combined-json abi

View File

@ -0,0 +1,3 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
contract C {}

View File

@ -0,0 +1 @@
{"contracts":{"combined_json_no_pretty_print/input.sol:C":{"abi":[]}},"version": "<VERSION REMOVED>"}

View File

@ -1 +1 @@
--combined-json ast --base-path . --allow-paths .
--combined-json ast --pretty-json --base-path . --allow-paths .

View File

@ -1 +1,111 @@
{"contracts":{"combined_json_with_base_path/c.sol:C":{}},"sourceList":["combined_json_with_base_path/c.sol","combined_json_with_base_path/input.sol"],"sources":{"combined_json_with_base_path/c.sol":{"AST":{"absolutePath":"combined_json_with_base_path/c.sol","exportedSymbols":{"C":[5]},"id":6,"license":"GPL-3.0","nodeType":"SourceUnit","nodes":[{"id":4,"literals":["solidity",">=","0.0"],"nodeType":"PragmaDirective","src":"36:22:0"},{"abstract":false,"baseContracts":[],"canonicalName":"C","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"id":5,"linearizedBaseContracts":[5],"name":"C","nameLocation":"69:1:0","nodeType":"ContractDefinition","nodes":[],"scope":6,"src":"60:13:0","usedErrors":[]}],"src":"36:38:0"}},"combined_json_with_base_path/input.sol":{"AST":{"absolutePath":"combined_json_with_base_path/input.sol","exportedSymbols":{"C":[5]},"id":3,"license":"GPL-3.0","nodeType":"SourceUnit","nodes":[{"id":1,"literals":["solidity",">=","0.0"],"nodeType":"PragmaDirective","src":"36:22:1"},{"absolutePath":"combined_json_with_base_path/c.sol","file":"./c.sol","id":2,"nameLocation":"-1:-1:-1","nodeType":"ImportDirective","scope":3,"sourceUnit":6,"src":"60:17:1","symbolAliases":[],"unitAlias":""}],"src":"36:42:1"}}},"version": "<VERSION REMOVED>"}
{
"contracts":
{
"combined_json_with_base_path/c.sol:C": {}
},
"sourceList":
[
"combined_json_with_base_path/c.sol",
"combined_json_with_base_path/input.sol"
],
"sources":
{
"combined_json_with_base_path/c.sol":
{
"AST":
{
"absolutePath": "combined_json_with_base_path/c.sol",
"exportedSymbols":
{
"C":
[
5
]
},
"id": 6,
"license": "GPL-3.0",
"nodeType": "SourceUnit",
"nodes":
[
{
"id": 4,
"literals":
[
"solidity",
">=",
"0.0"
],
"nodeType": "PragmaDirective",
"src": "36:22:0"
},
{
"abstract": false,
"baseContracts": [],
"canonicalName": "C",
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 5,
"linearizedBaseContracts":
[
5
],
"name": "C",
"nameLocation": "69:1:0",
"nodeType": "ContractDefinition",
"nodes": [],
"scope": 6,
"src": "60:13:0",
"usedErrors": []
}
],
"src": "36:38:0"
}
},
"combined_json_with_base_path/input.sol":
{
"AST":
{
"absolutePath": "combined_json_with_base_path/input.sol",
"exportedSymbols":
{
"C":
[
5
]
},
"id": 3,
"license": "GPL-3.0",
"nodeType": "SourceUnit",
"nodes":
[
{
"id": 1,
"literals":
[
"solidity",
">=",
"0.0"
],
"nodeType": "PragmaDirective",
"src": "36:22:1"
},
{
"absolutePath": "combined_json_with_base_path/c.sol",
"file": "./c.sol",
"id": 2,
"nameLocation": "-1:-1:-1",
"nodeType": "ImportDirective",
"scope": 3,
"sourceUnit": 6,
"src": "60:17:1",
"symbolAliases": [],
"unitAlias": ""
}
],
"src": "36:42:1"
}
}
},
"version": "<VERSION REMOVED>"
}

View File

@ -0,0 +1 @@
--combined-json devdoc --pretty-json --allow-paths .

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
/// @dev This is devdoc.
contract C {}
/// And this is a notice.
contract D {}

View File

@ -0,0 +1,25 @@
{
"contracts":
{
"combined_json_with_devdoc/input.sol:C":
{
"devdoc":
{
"details": "This is devdoc.",
"kind": "dev",
"methods": {},
"version": 1
}
},
"combined_json_with_devdoc/input.sol:D":
{
"devdoc":
{
"kind": "dev",
"methods": {},
"version": 1
}
}
},
"version": "<VERSION REMOVED>"
}

View File

@ -0,0 +1 @@
--combined-json userdoc --pretty-json --allow-paths .

View File

@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.0;
/// @notice Description for users.
contract C {}
/// @dev Description for developers.
contract D {}

View File

@ -0,0 +1,25 @@
{
"contracts":
{
"combined_json_with_userdoc/input.sol:C":
{
"userdoc":
{
"kind": "user",
"methods": {},
"notice": "Description for users.",
"version": 1
}
},
"combined_json_with_userdoc/input.sol:D":
{
"userdoc":
{
"kind": "user",
"methods": {},
"version": 1
}
}
},
"version": "<VERSION REMOVED>"
}

View File

@ -13,7 +13,7 @@
},
"calldata_array_index_access_uint256_dyn_calldata":
{
"entryPoint": 145,
"entryPoint": 144,
"parameterSlots": 2,
"returnSlots": 1
}

View File

@ -1 +1 @@
--error-recovery --ast-compact-json --hashes
--error-recovery --ast-compact-json --pretty-json --hashes

View File

@ -0,0 +1,10 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C {}"
}
}
}

View File

@ -0,0 +1 @@
{"sources":{"A":{"id":0}}}

View File

@ -1 +1 @@
--storage-layout
--storage-layout --pretty-json

View File

@ -1,4 +1,87 @@
======= storage_layout_user_defined/input.sol:C =======
Contract Storage Layout:
{"storage":[{"astId":7,"contract":"storage_layout_user_defined/input.sol:C","label":"a","offset":0,"slot":"0","type":"t_userDefinedValueType(MyInt128)2"},{"astId":10,"contract":"storage_layout_user_defined/input.sol:C","label":"b","offset":16,"slot":"0","type":"t_userDefinedValueType(MyInt128)2"},{"astId":13,"contract":"storage_layout_user_defined/input.sol:C","label":"c","offset":0,"slot":"1","type":"t_userDefinedValueType(MyInt128)2"},{"astId":16,"contract":"storage_layout_user_defined/input.sol:C","label":"d","offset":16,"slot":"1","type":"t_userDefinedValueType(MyInt8)4"},{"astId":19,"contract":"storage_layout_user_defined/input.sol:C","label":"e","offset":17,"slot":"1","type":"t_userDefinedValueType(MyInt8)4"},{"astId":22,"contract":"storage_layout_user_defined/input.sol:C","label":"f","offset":18,"slot":"1","type":"t_userDefinedValueType(MyInt8)4"},{"astId":25,"contract":"storage_layout_user_defined/input.sol:C","label":"g","offset":19,"slot":"1","type":"t_userDefinedValueType(MyInt8)4"},{"astId":28,"contract":"storage_layout_user_defined/input.sol:C","label":"h","offset":20,"slot":"1","type":"t_userDefinedValueType(MyInt8)4"}],"types":{"t_userDefinedValueType(MyInt128)2":{"encoding":"inplace","label":"MyInt128","numberOfBytes":"16"},"t_userDefinedValueType(MyInt8)4":{"encoding":"inplace","label":"MyInt8","numberOfBytes":"1"}}}
{
"storage":
[
{
"astId": 7,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "a",
"offset": 0,
"slot": "0",
"type": "t_userDefinedValueType(MyInt128)2"
},
{
"astId": 10,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "b",
"offset": 16,
"slot": "0",
"type": "t_userDefinedValueType(MyInt128)2"
},
{
"astId": 13,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "c",
"offset": 0,
"slot": "1",
"type": "t_userDefinedValueType(MyInt128)2"
},
{
"astId": 16,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "d",
"offset": 16,
"slot": "1",
"type": "t_userDefinedValueType(MyInt8)4"
},
{
"astId": 19,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "e",
"offset": 17,
"slot": "1",
"type": "t_userDefinedValueType(MyInt8)4"
},
{
"astId": 22,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "f",
"offset": 18,
"slot": "1",
"type": "t_userDefinedValueType(MyInt8)4"
},
{
"astId": 25,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "g",
"offset": 19,
"slot": "1",
"type": "t_userDefinedValueType(MyInt8)4"
},
{
"astId": 28,
"contract": "storage_layout_user_defined/input.sol:C",
"label": "h",
"offset": 20,
"slot": "1",
"type": "t_userDefinedValueType(MyInt8)4"
}
],
"types":
{
"t_userDefinedValueType(MyInt128)2":
{
"encoding": "inplace",
"label": "MyInt128",
"numberOfBytes": "16"
},
"t_userDefinedValueType(MyInt8)4":
{
"encoding": "inplace",
"label": "MyInt8",
"numberOfBytes": "1"
}
}
}

View File

@ -0,0 +1 @@
--storage-layout

View File

@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL v3
pragma solidity >=0.0;
contract C {}

View File

@ -0,0 +1,4 @@
======= storage_layout_user_defined_no_pretty_print/input.sol:C =======
Contract Storage Layout:
{"storage":[]}

View File

@ -125,7 +125,6 @@ sub_0: assembly {
eq
tag_4
jumpi
pop
0x00
dup1
revert

View File

@ -88,18 +88,6 @@ contract MultiSigWallet {
_;
}
/// @dev Receive function allows to deposit ether.
receive()
external
payable
{
if (msg.value > 0)
emit Deposit(msg.sender, msg.value);
}
/*
* Public functions
*/
/// @dev Contract constructor sets initial owners and required number of confirmations.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
@ -115,6 +103,19 @@ contract MultiSigWallet {
required = _required;
}
/// @dev Receive function allows to deposit ether.
receive()
external
payable
{
if (msg.value > 0)
emit Deposit(msg.sender, msg.value);
}
/*
* Public functions
*/
/// @dev Allows to add a new owner. Transaction has to be sent by wallet.
/// @param owner Address of new owner.
function addOwner(address owner)
@ -225,9 +226,9 @@ contract MultiSigWallet {
notExecuted(transactionId)
{
if (isConfirmed(transactionId)) {
Transaction storage tx = transactions[transactionId];
(tx.executed,) = tx.destination.call{value: tx.value}(tx.data);
if (tx.executed)
Transaction storage transaction = transactions[transactionId];
(transaction.executed,) = transaction.destination.call{value: transaction.value}(transaction.data);
if (transaction.executed)
emit Execution(transactionId);
else
emit ExecutionFailure(transactionId);
@ -249,30 +250,7 @@ contract MultiSigWallet {
if (count == required)
return true;
}
}
/*
* Internal functions
*/
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return transactionId Returns transaction ID.
function addTransaction(address destination, uint value, bytes memory data)
internal
notNull(destination)
returns (uint transactionId)
{
transactionId = transactionCount;
transactions[transactionId] = Transaction({
destination: destination,
value: value,
data: data,
executed: false
});
transactionCount += 1;
emit Submission(transactionId);
return false;
}
/*
@ -362,4 +340,27 @@ contract MultiSigWallet {
for (i=from; i<to; i++)
_transactionIds[i - from] = transactionIdsTemp[i];
}
}
/*
* Internal functions
*/
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param destination Transaction target address.
/// @param value Transaction ether value.
/// @param data Transaction data payload.
/// @return transactionId Returns transaction ID.
function addTransaction(address destination, uint value, bytes memory data)
internal
notNull(destination)
returns (uint transactionId)
{
transactionId = transactionCount;
transactions[transactionId] = Transaction({
destination: destination,
value: value,
data: data,
executed: false
});
transactionCount += 1;
emit Submission(transactionId);
}
}

View File

@ -37,16 +37,17 @@ verify_input "$@"
printTask "Running external tests..."
"{$REPO_ROOT}/test/externalTests/zeppelin.sh" "$@"
"{$REPO_ROOT}/test/externalTests/gnosis.sh" "$@"
"{$REPO_ROOT}/test/externalTests/colony.sh" "$@"
"{$REPO_ROOT}/test/externalTests/ens.sh" "$@"
"{$REPO_ROOT}/test/externalTests/trident.sh" "$@"
"{$REPO_ROOT}/test/externalTests/euler.sh" "$@"
"{$REPO_ROOT}/test/externalTests/yield-liquidator.sh" "$@"
"{$REPO_ROOT}/test/externalTests/bleeps.sh" "$@"
"{$REPO_ROOT}/test/externalTests/pool-together.sh" "$@"
"{$REPO_ROOT}/test/externalTests/perpetual-pools.sh" "$@"
"{$REPO_ROOT}/test/externalTests/uniswap.sh" "$@"
"{$REPO_ROOT}/test/externalTests/prb-math.sh" "$@"
"{$REPO_ROOT}/test/externalTests/elementfi.sh" "$@"
"${REPO_ROOT}/test/externalTests/zeppelin.sh" "$@"
"${REPO_ROOT}/test/externalTests/gnosis.sh" "$@"
"${REPO_ROOT}/test/externalTests/colony.sh" "$@"
"${REPO_ROOT}/test/externalTests/ens.sh" "$@"
"${REPO_ROOT}/test/externalTests/trident.sh" "$@"
"${REPO_ROOT}/test/externalTests/euler.sh" "$@"
"${REPO_ROOT}/test/externalTests/yield-liquidator.sh" "$@"
"${REPO_ROOT}/test/externalTests/bleeps.sh" "$@"
"${REPO_ROOT}/test/externalTests/pool-together.sh" "$@"
"${REPO_ROOT}/test/externalTests/perpetual-pools.sh" "$@"
"${REPO_ROOT}/test/externalTests/uniswap.sh" "$@"
"${REPO_ROOT}/test/externalTests/prb-math.sh" "$@"
"${REPO_ROOT}/test/externalTests/elementfi.sh" "$@"
"${REPO_ROOT}/test/externalTests/brink.sh" "$@"

83
test/externalTests/brink.sh Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# 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/>
#
# (c) 2022 solidity contributors.
#------------------------------------------------------------------------------
set -e
source scripts/common.sh
source test/externalTests/common.sh
REPO_ROOT=$(realpath "$(dirname "$0")/../..")
verify_input "$@"
BINARY_TYPE="$1"
BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { yarn compile; }
function test_fn { SNAPSHOT_UPDATE=1 npx --no hardhat test; }
function brink_test
{
local repo="https://github.com/brinktrade/brink-core"
local ref_type=branch
local ref=master
local config_file="hardhat.config.js"
local config_var=""
local extra_settings="metadata: {bytecodeHash: 'none'}"
local extra_optimizer_settings="runs: 800"
local compile_only_presets=(
#ir-no-optimize # Compilation fails with "YulException: Variable var_signature_127_offset is 2 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with "YulException: Variable var_signature_127_offset is 2 slot(s) too deep inside the stack."
ir-optimize-evm+yul # Lots of test failures. Tests depend on constants.js, which seems to be calculated specifically for 0.8.10.
legacy-optimize-evm+yul # Lots of test failures. Tests depend on constants.js, which seems to be calculated specifically for 0.8.10.
legacy-no-optimize # Lots of test failures. Tests depend on constants.js, which seems to be calculated specifically for 0.8.10.
legacy-optimize-evm-only # Lots of test failures. Tests depend on constants.js, which seems to be calculated specifically for 0.8.10.
)
local settings_presets=(
"${compile_only_presets[@]}"
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$ref_type" "$ref" "$DIR"
neutralize_package_lock
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var" "$CURRENT_EVM_VERSION" "$extra_settings" "$extra_optimizer_settings"
yarn install
yarn add hardhat-gas-reporter
# TODO: Remove when https://github.com/brinktrade/brink-core/issues/48 is fixed.
yarn add chai
replace_version_pragmas
for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var" "$extra_settings" "$extra_optimizer_settings"
store_benchmark_report hardhat brink "$repo" "$preset"
done
}
external_test Brink brink_test

View File

@ -198,6 +198,8 @@ function force_truffle_compiler_settings
local solc_path="$3"
local preset="$4"
local evm_version="${5:-"$CURRENT_EVM_VERSION"}"
local extra_settings="$6"
local extra_optimizer_settings="$7"
[[ $binary_type == native || $binary_type == solcjs ]] || assertFail
@ -209,14 +211,14 @@ function force_truffle_compiler_settings
echo "Binary type: $binary_type"
echo "Compiler path: $solc_path"
echo "Settings preset: ${preset}"
echo "Settings: $(settings_from_preset "$preset" "$evm_version")"
echo "Settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")"
echo "EVM version: $evm_version"
echo "Compiler version: ${SOLCVERSION_SHORT}"
echo "Compiler version (full): ${SOLCVERSION}"
echo "-------------------------------------"
local compiler_settings gas_reporter_settings
compiler_settings=$(truffle_compiler_settings "$solc_path" "$preset" "$evm_version")
compiler_settings=$(truffle_compiler_settings "$solc_path" "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")
gas_reporter_settings=$(eth_gas_reporter_settings "$preset")
{
@ -307,19 +309,21 @@ function force_hardhat_compiler_settings
local preset="$2"
local config_var_name="$3"
local evm_version="${4:-"$CURRENT_EVM_VERSION"}"
local extra_settings="$5"
local extra_optimizer_settings="$6"
printLog "Configuring Hardhat..."
echo "-------------------------------------"
echo "Config file: ${config_file}"
echo "Settings preset: ${preset}"
echo "Settings: $(settings_from_preset "$preset" "$evm_version")"
echo "Settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")"
echo "EVM version: ${evm_version}"
echo "Compiler version: ${SOLCVERSION_SHORT}"
echo "Compiler version (full): ${SOLCVERSION}"
echo "-------------------------------------"
local compiler_settings gas_reporter_settings
compiler_settings=$(hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version")
compiler_settings=$(hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")
gas_reporter_settings=$(eth_gas_reporter_settings "$preset")
if [[ $config_file == *\.js ]]; then
[[ $config_var_name == "" ]] || assertFail
@ -372,17 +376,22 @@ function settings_from_preset
{
local preset="$1"
local evm_version="$2"
local extra_settings="$3"
local extra_optimizer_settings="$4"
[[ " ${AVAILABLE_PRESETS[*]} " == *" $preset "* ]] || assertFail
[[ $extra_settings == "" ]] || extra_settings="${extra_settings}, "
[[ $extra_optimizer_settings == "" ]] || extra_optimizer_settings="${extra_optimizer_settings}, "
case "$preset" in
# NOTE: Remember to update `parallelism` of `t_ems_ext` job in CI config if you add/remove presets
legacy-no-optimize) echo "{evmVersion: '${evm_version}', viaIR: false, optimizer: {enabled: false}}" ;;
ir-no-optimize) echo "{evmVersion: '${evm_version}', viaIR: true, optimizer: {enabled: false}}" ;;
legacy-optimize-evm-only) echo "{evmVersion: '${evm_version}', viaIR: false, optimizer: {enabled: true, details: {yul: false}}}" ;;
ir-optimize-evm-only) echo "{evmVersion: '${evm_version}', viaIR: true, optimizer: {enabled: true, details: {yul: false}}}" ;;
legacy-optimize-evm+yul) echo "{evmVersion: '${evm_version}', viaIR: false, optimizer: {enabled: true, details: {yul: true}}}" ;;
ir-optimize-evm+yul) echo "{evmVersion: '${evm_version}', viaIR: true, optimizer: {enabled: true, details: {yul: true}}}" ;;
legacy-no-optimize) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: false, optimizer: {${extra_optimizer_settings}enabled: false}}" ;;
ir-no-optimize) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: true, optimizer: {${extra_optimizer_settings}enabled: false}}" ;;
legacy-optimize-evm-only) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: false, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: false}}}" ;;
ir-optimize-evm-only) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: true, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: false}}}" ;;
legacy-optimize-evm+yul) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: false, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: true}}}" ;;
ir-optimize-evm+yul) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: true, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: true}}}" ;;
*)
fail "Unknown settings preset: '${preset}'."
;;
@ -419,11 +428,13 @@ function truffle_compiler_settings
local solc_path="$1"
local preset="$2"
local evm_version="$3"
local extra_settings="$4"
local extra_optimizer_settings="$5"
echo "{"
echo " solc: {"
echo " version: \"${solc_path}\","
echo " settings: $(settings_from_preset "$preset" "$evm_version")"
echo " settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")"
echo " }"
echo "}"
}
@ -468,10 +479,12 @@ function hardhat_compiler_settings {
local solc_version="$1"
local preset="$2"
local evm_version="$3"
local extra_settings="$4"
local extra_optimizer_settings="$5"
echo "{"
echo " version: '${solc_version}',"
echo " settings: $(settings_from_preset "$preset" "$evm_version")"
echo " settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")"
echo "}"
}
@ -506,9 +519,11 @@ function truffle_run_test
local compile_only_presets="$5"
local compile_fn="$6"
local test_fn="$7"
local extra_settings="$8"
local extra_optimizer_settings="$9"
truffle_clean
force_truffle_compiler_settings "$config_file" "$binary_type" "$solc_path" "$preset"
force_truffle_compiler_settings "$config_file" "$binary_type" "$solc_path" "$preset" "$CURRENT_EVM_VERSION" "$extra_settings" "$extra_optimizer_settings"
compile_and_run_test compile_fn test_fn truffle_verify_compiler_version "$preset" "$compile_only_presets"
}
@ -520,9 +535,11 @@ function hardhat_run_test
local compile_fn="$4"
local test_fn="$5"
local config_var_name="$6"
local extra_settings="$7"
local extra_optimizer_settings="$8"
hardhat_clean
force_hardhat_compiler_settings "$config_file" "$preset" "$config_var_name"
force_hardhat_compiler_settings "$config_file" "$preset" "$config_var_name" "$CURRENT_EVM_VERSION" "$extra_settings" "$extra_optimizer_settings"
compile_and_run_test compile_fn test_fn hardhat_verify_compiler_version "$preset" "$compile_only_presets"
}

View File

@ -42,9 +42,9 @@ function elementfi_test
local config_file="hardhat.config.ts"
local config_var=config
local compile_only_presets=()
local settings_presets=(
"${compile_only_presets[@]}"
local compile_only_presets=(
# ElementFi's test suite is hard-coded for mainnet forked via alchemy.io.
# Locally we can only compile.
#ir-no-optimize # Compilation fails with "YulException: Variable var_amount_9311 is 10 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with "YulException: Variable var_amount_9311 is 10 slot(s) too deep inside the stack."
ir-optimize-evm+yul
@ -52,6 +52,9 @@ function elementfi_test
legacy-optimize-evm-only
legacy-optimize-evm+yul
)
local settings_presets=(
"${compile_only_presets[@]}"
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"

View File

@ -65,6 +65,10 @@ function ens_test
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")"
yarn install
# With ethers.js 5.6.2 many tests for revert messages fail.
# TODO: Remove when https://github.com/ethers-io/ethers.js/discussions/2849 is resolved.
yarn add ethers@5.6.1
replace_version_pragmas
neutralize_packaged_contracts

View File

@ -68,6 +68,10 @@ function euler_test
force_hardhat_unlimited_contract_size "$config_file"
npm install
# With ethers.js 5.6.2 many tests for revert messages fail.
# TODO: Remove when https://github.com/ethers-io/ethers.js/discussions/2849 is resolved.
npm install ethers@5.6.1
replace_version_pragmas
neutralize_packaged_contracts

View File

@ -81,6 +81,10 @@ function gnosis_safe_test
npm install
npm install hardhat-gas-reporter
# With ethers.js 5.6.2 many tests for revert messages fail.
# TODO: Remove when https://github.com/ethers-io/ethers.js/discussions/2849 is resolved.
npm install ethers@5.6.1
replace_version_pragmas
[[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc/dist"

View File

@ -66,10 +66,9 @@ function perpetual_pools_test
force_hardhat_unlimited_contract_size "$config_file" "$config_var"
yarn install
# The project depends on @openzeppelin/hardhat-upgrades, which is currently not prepared
# for the parallel compilation introduced in Hardhat 2.9.0.
# TODO: Remove when https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/528 is fixed.
yarn add hardhat@2.8.4
# With ethers.js 5.6.2 many tests for revert messages fail.
# TODO: Remove when https://github.com/ethers-io/ethers.js/discussions/2849 is resolved.
yarn add ethers@5.6.1
replace_version_pragmas

View File

@ -77,6 +77,10 @@ function uniswap_test
yarn install
yarn add hardhat-gas-reporter
# With ethers.js 5.6.2 many tests for revert messages fail.
# TODO: Remove when https://github.com/ethers-io/ethers.js/discussions/2849 is resolved.
yarn add ethers@5.6.1
replace_version_pragmas
for preset in $SELECTED_PRESETS; do

View File

@ -70,6 +70,8 @@ function zeppelin_test
# In some cases Hardhat does not detect revert reasons properly via IR.
# TODO: Remove this when https://github.com/NomicFoundation/hardhat/issues/2453 gets fixed.
sed -i "s|it(\('reverts if the current value is 0'\)|it.skip(\1|g" test/utils/Counters.test.js
# TODO: Remove this when https://github.com/NomicFoundation/hardhat/issues/2115 gets fixed.
sed -i "s|describe\(('Polygon-Child'\)|describe.skip\1|g" test/crosschain/CrossChainEnabled.test.js
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"

View File

@ -56,7 +56,8 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
{
map<string, unsigned> indices = {
{ "root.asm", 0 },
{ "sub.asm", 1 }
{ "sub.asm", 1 },
{ "verbatim.asm", 2 }
};
Assembly _assembly{false, {}};
auto root_asm = make_shared<string>("root.asm");
@ -65,11 +66,22 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
Assembly _subAsm{false, {}};
auto sub_asm = make_shared<string>("sub.asm");
_subAsm.setSourceLocation({6, 8, sub_asm});
Assembly _verbatimAsm(true, "");
auto verbatim_asm = make_shared<string>("verbatim.asm");
_verbatimAsm.setSourceLocation({8, 18, verbatim_asm});
// PushImmutable
_subAsm.appendImmutable("someImmutable");
_subAsm.append(AssemblyItem(PushTag, 0));
_subAsm.append(Instruction::INVALID);
shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm);
_verbatimAsm.appendVerbatim({0xff,0xff}, 0, 0);
_verbatimAsm.appendVerbatim({0x74, 0x65, 0x73, 0x74}, 0, 1);
_verbatimAsm.append(Instruction::MSTORE);
shared_ptr<Assembly> _verbatimAsmPtr = make_shared<Assembly>(_verbatimAsm);
// Tag
auto tag = _assembly.newTag();
_assembly.append(tag);
@ -77,7 +89,10 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
_assembly.append(u256(1));
_assembly.append(u256(2));
// Push
_assembly.append(Instruction::KECCAK256);
auto keccak256 = AssemblyItem(Instruction::KECCAK256);
_assembly.m_currentModifierDepth = 1;
_assembly.append(keccak256);
_assembly.m_currentModifierDepth = 0;
// PushProgramSize
_assembly.appendProgramSize();
// PushLibraryAddress
@ -90,6 +105,10 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
auto sub = _assembly.appendSubroutine(_subAsmPtr);
// PushSub
_assembly.pushSubroutineOffset(static_cast<size_t>(sub.data()));
// PushSubSize
auto verbatim_sub = _assembly.appendSubroutine(_verbatimAsmPtr);
// PushSub
_assembly.pushSubroutineOffset(static_cast<size_t>(verbatim_sub.data()));
// PushDeployTimeAddress
_assembly.append(PushDeployTimeAddress);
// AssignImmutable.
@ -102,16 +121,21 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
_assembly.appendToAuxiliaryData(bytes{0x42, 0x66});
_assembly.appendToAuxiliaryData(bytes{0xee, 0xaa});
_assembly.m_currentModifierDepth = 2;
_assembly.appendJump(tag);
_assembly.m_currentModifierDepth = 0;
checkCompilation(_assembly);
BOOST_CHECK_EQUAL(
_assembly.assemble().toHex(),
"5b6001600220606f73__$bf005014d9d0f534b8fcb268bd84c491a2$__"
"6000566067602260457300000000000000000000000000000000000000005050"
"5b6001600220607f73__$bf005014d9d0f534b8fcb268bd84c491a2$__"
"60005660776024604c600760707300000000000000000000000000000000000000005050"
"600260010152"
"00fe"
"006000"
"56fe"
"7f0000000000000000000000000000000000000000000000000000000000000000"
"fe010203044266eeaa"
"6000feffff7465737452010203044266eeaa"
);
BOOST_CHECK_EQUAL(
_assembly.assemblyString(),
@ -124,30 +148,40 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
" data_a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b\n"
" dataSize(sub_0)\n"
" dataOffset(sub_0)\n"
" dataSize(sub_1)\n"
" dataOffset(sub_1)\n"
" deployTimeAddress()\n"
" assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n"
" 0x02\n"
" assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" stop\n"
" jump(tag_1)\n"
"stop\n"
"data_a6885b3731702da62e8e4a8f584ac46a7f6822f4e2ba50fba902f67b1588d23b 01020304\n"
"\n"
"sub_0: assembly {\n"
" /* \"sub.asm\":6:8 */\n"
" immutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" tag_0\n"
" invalid\n"
"}\n"
"\n"
"sub_1: assembly {\n"
" /* \"verbatim.asm\":8:18 */\n"
" verbatimbytecode_ffff\n"
" verbatimbytecode_74657374\n"
" mstore\n"
"}\n"
"\n"
"auxdata: 0x4266eeaa\n"
);
BOOST_CHECK_EQUAL(
util::jsonCompactPrint(_assembly.assemblyJSON(indices)),
string json{
"{\".auxdata\":\"4266eeaa\",\".code\":["
"{\"begin\":1,\"end\":3,\"name\":\"tag\",\"source\":0,\"value\":\"1\"},"
"{\"begin\":1,\"end\":3,\"name\":\"JUMPDEST\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"1\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2\"},"
"{\"begin\":1,\"end\":3,\"name\":\"KECCAK256\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"modifierDepth\":1,\"name\":\"KECCAK256\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSHSIZE\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSHLIB\",\"source\":0,\"value\":\"someLibrary\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH [tag]\",\"source\":0,\"value\":\"1\"},"
@ -155,16 +189,28 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
"{\"begin\":1,\"end\":3,\"name\":\"PUSH data\",\"source\":0,\"value\":\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000001\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000001\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSHDEPLOYADDRESS\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2\"},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"STOP\",\"source\":0}"
"{\"begin\":1,\"end\":3,\"name\":\"STOP\",\"source\":0},"
"{\"begin\":1,\"end\":3,\"modifierDepth\":2,\"name\":\"PUSH [tag]\",\"source\":0,\"value\":\"1\"},{\"begin\":1,\"end\":3,\"modifierDepth\":2,\"name\":\"JUMP\",\"source\":0}"
"],\".data\":{\"0\":{\".code\":["
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"},"
"{\"begin\":6,\"end\":8,\"name\":\"PUSH [ErrorTag]\",\"source\":1},"
"{\"begin\":6,\"end\":8,\"name\":\"INVALID\",\"source\":1}"
"]},\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\":\"01020304\"}}"
);
"]},"
"\"1\":{\".code\":["
"{\"begin\":8,\"end\":18,\"name\":\"VERBATIM\",\"source\":2,\"value\":\"ffff\"},"
"{\"begin\":8,\"end\":18,\"name\":\"VERBATIM\",\"source\":2,\"value\":\"74657374\"},"
"{\"begin\":8,\"end\":18,\"name\":\"MSTORE\",\"source\":2}"
"]},\"A6885B3731702DA62E8E4A8F584AC46A7F6822F4E2BA50FBA902F67B1588D23B\":\"01020304\"},\"sourceList\":[\"root.asm\",\"sub.asm\",\"verbatim.asm\"]}"
};
Json::Value jsonValue;
BOOST_CHECK(util::jsonParseStrict(json, jsonValue));
BOOST_CHECK_EQUAL(util::jsonCompactPrint(_assembly.assemblyJSON(indices)), util::jsonCompactPrint(jsonValue));
}
BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps)
@ -343,7 +389,7 @@ BOOST_AUTO_TEST_CASE(immutable)
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"},"
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someOtherImmutable\"},"
"{\"begin\":6,\"end\":8,\"name\":\"PUSHIMMUTABLE\",\"source\":1,\"value\":\"someImmutable\"}"
"]}}}"
"]}},\"sourceList\":[\"root.asm\",\"sub.asm\"]}"
);
}

Some files were not shown because too many files have changed in this diff Show More